Scrapy學(xué)習(xí)筆記

scrapy是python最有名的爬蟲框架之一局冰,可以很方便的進(jìn)行web抓取,并且提供了很強(qiáng)的定制型,這里記錄簡(jiǎn)單學(xué)習(xí)的過(guò)程和在實(shí)際應(yīng)用中會(huì)遇到的一些常見(jiàn)問(wèn)題

一傻寂、安裝

在安裝scrapy之前有一些依賴需要安裝完沪,否則可能會(huì)安裝失敗域庇,scrapy的選擇器依賴于lxml,還有Twisted網(wǎng)絡(luò)引擎覆积,下面是ubuntu下安裝的過(guò)程

1. linux下安裝

# 1. 安裝xml依賴庫(kù)
$ sudo apt-get install libxml2 libxml2-dev
$ sudo apt-get install libxslt1-dev
$ sudo apt-get install python-libxml2

# 2. 安裝lxml
$ sudo pip install lxml

# 3. 安裝Twisted(版本可以換成最新的)听皿,用pip也可以,如果失敗的話下載源碼安裝宽档,如下
$ wget https://pypi.python.org/packages/6b/23/8dbe86fc83215015e221fbd861a545c6ec5c9e9cd7514af114d1f64084ab/Twisted-16.4.1.tar.bz2#md5=c6d09bdd681f538369659111f079c29d
$ tar xjf Twisted-16.4.1.tar.bz2
$ cd Twisted-16.4.1
$ sudo python setup.py install

# 3. 安裝scrapy
$ sudo pip install scrapy

http://lxml.de/installation.html

2. Mac下安裝

# 安裝xml依賴庫(kù)
$ xcode-select —install

# 其實(shí)相關(guān)依賴pip會(huì)自動(dòng)幫我們裝上
$ pip install scrapy

mac下安裝有時(shí)候會(huì)失敗尉姨,建議使用virtualenv安裝在獨(dú)立的環(huán)境下,可以減少一些問(wèn)題吗冤,因?yàn)閙ac系統(tǒng)自帶python又厉,例如一些依賴庫(kù)依賴的一些新的版本九府,而升級(jí)新版本會(huì)把舊版本卸載掉,卸載可能會(huì)有權(quán)限的問(wèn)題

二馋没、基本使用

1. 初始化scrapy項(xiàng)目

我們可以使用命令行初始化一個(gè)項(xiàng)目

$ scrapy startproject tutorial

這里可以查看scrapy更多其他的命令

初始化完成后昔逗,我們得到下面目錄結(jié)構(gòu)

scrapy.cfg:         項(xiàng)目的配置文件
tutorial/:          該項(xiàng)目的python模塊, 在這里添加代碼
    items.py:       項(xiàng)目中的item文件
    pipelines.py:   項(xiàng)目中的pipelines文件.
    settings.py:    項(xiàng)目全局設(shè)置文件.
    spiders/        爬蟲模塊目錄

我們先看一下scrapy的處理流程


流程圖

scrapy由下面幾個(gè)部分組成

  • spiders:爬蟲模塊,負(fù)責(zé)配置需要爬取的數(shù)據(jù)和爬取規(guī)則篷朵,以及解析結(jié)構(gòu)化數(shù)據(jù)
  • items:定義我們需要的結(jié)構(gòu)化數(shù)據(jù)勾怒,使用相當(dāng)于dict
  • pipelines:管道模塊,處理spider模塊分析好的結(jié)構(gòu)化數(shù)據(jù)声旺,如保存入庫(kù)等
  • middlewares:中間件笔链,相當(dāng)于鉤子,可以對(duì)爬取前后做預(yù)處理腮猖,如修改請(qǐng)求header鉴扫,url過(guò)濾等

我們先來(lái)看一個(gè)例子,在spiders目錄下新建一個(gè)模塊DmozSpider.py

import scrapy

class DmozSpider(scrapy.Spider):
    # 必須定義
    name = "dmoz"
    # 初始urls
    start_urls = [
        "http://www.dmoz.org/Computers/Programming/Languages/Python/Books/",
        "http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/"
    ]

    # 默認(rèn)response處理函數(shù)
    def parse(self, response):
        # 把結(jié)果寫到文件中
        filename = response.url.split("/")[-2]
        with open(filename, 'wb') as f:
            f.write(response.body)

打開終端進(jìn)入根目錄澈缺,執(zhí)行下面命令

$ scrapy crawl dmoz

爬蟲開始爬取start_urls定義的url坪创,并輸出到文件中,最后輸出爬去報(bào)告姐赡,會(huì)輸出爬取得統(tǒng)計(jì)結(jié)果

2016-09-13 10:36:43 [scrapy] INFO: Spider opened
2016-09-13 10:36:43 [scrapy] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2016-09-13 10:36:43 [scrapy] DEBUG: Telnet console listening on 127.0.0.1:6023
2016-09-13 10:36:44 [scrapy] DEBUG: Crawled (200) <GET http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/> (referer: None)
2016-09-13 10:36:45 [scrapy] DEBUG: Crawled (200) <GET http://www.dmoz.org/Computers/Programming/Languages/Python/Books/> (referer: None)
2016-09-13 10:36:45 [scrapy] INFO: Closing spider (finished)
2016-09-13 10:36:45 [scrapy] INFO: Dumping Scrapy stats:
{'downloader/request_bytes': 548,
 'downloader/request_count': 2,
 'downloader/request_method_count/GET': 2,
 'downloader/response_bytes': 16179,
 'downloader/response_count': 2,
 'downloader/response_status_count/200': 2,
 'finish_reason': 'finished',
 'finish_time': datetime.datetime(2016, 9, 13, 2, 36, 45, 585113),
 'log_count/DEBUG': 3,
 'log_count/INFO': 7,
 'response_received_count': 2,
 'scheduler/dequeued': 2,
 'scheduler/dequeued/memory': 2,
 'scheduler/enqueued': 2,
 'scheduler/enqueued/memory': 2,
 'start_time': datetime.datetime(2016, 9, 13, 2, 36, 43, 935790)}
2016-09-13 10:36:45 [scrapy] INFO: Spider closed (finished)

這里我們完成了簡(jiǎn)單的爬取和保存的操作莱预,會(huì)在根目錄生成兩個(gè)文件ResourcesBooks

2. 通過(guò)代碼運(yùn)行爬蟲

每次進(jìn)入控制臺(tái)運(yùn)行爬蟲還是比較麻煩的,而且不好調(diào)試项滑,我們可以通過(guò)CrawlerProcess通過(guò)代碼運(yùn)行爬蟲依沮,新建一個(gè)模塊run.py

from scrapy.crawler import CrawlerProcess
from scrapy.utils.project import get_project_settings

from spiders.DmozSpider import DmozSpider

# 獲取settings.py模塊的設(shè)置
settings = get_project_settings()
process = CrawlerProcess(settings=settings)

# 可以添加多個(gè)spider
# process.crawl(Spider1)
# process.crawl(Spider2)
process.crawl(DmozSpider)

# 啟動(dòng)爬蟲,會(huì)阻塞枪狂,直到爬取完成
process.start()

參考:http://doc.scrapy.org/en/latest/topics/practices.html#run-scrapy-from-a-script

三危喉、Scrapy類

如上面的DmozSpider類,爬蟲類繼承自scrapy.Spider州疾,用于構(gòu)造Request對(duì)象給Scheduler

1. 常用屬性與方法

屬性

  • name:爬蟲的名字辜限,必須唯一(如果在控制臺(tái)使用的話,必須配置)
  • start_urls:爬蟲初始爬取的鏈接列表
  • parse:response結(jié)果處理函數(shù)
  • custom_settings:自定義配置严蓖,覆蓋settings.py中的默認(rèn)配置

方法

  • start_requests:?jiǎn)?dòng)爬蟲的時(shí)候調(diào)用列粪,默認(rèn)是調(diào)用make_requests_from_url方法爬取start_urls的鏈接,可以在這個(gè)方法里面定制谈飒,如果重寫了該方法,start_urls默認(rèn)將不會(huì)被使用态蒂,可以在這個(gè)方法里面定制一些自定義的url杭措,如登錄,從數(shù)據(jù)庫(kù)讀取url等钾恢,本方法返回Request對(duì)象
  • make_requests_from_url:默認(rèn)由start_requests調(diào)用手素,可以配置Request對(duì)象鸳址,返回Request對(duì)象
  • parse:response到達(dá)spider的時(shí)候默認(rèn)調(diào)用,如果在Request對(duì)象配置了callback函數(shù)泉懦,則不會(huì)調(diào)用稿黍,parse方法可以迭代返回ItemRequest對(duì)象,如果返回Request對(duì)象崩哩,則會(huì)進(jìn)行增量爬取

2. Request與Response對(duì)象

每個(gè)請(qǐng)求都是一個(gè)Request對(duì)象巡球,Request對(duì)象定義了請(qǐng)求的相關(guān)信息(url, method, headers, body, cookie, priority)和回調(diào)的相關(guān)信息(meta, callback, dont_filter, errback),通常由spider迭代返回

其中meta相當(dāng)于附加變量邓嘹,可以在請(qǐng)求完成后通過(guò)response.meta訪問(wèn)

請(qǐng)求完成后酣栈,會(huì)通過(guò)Response對(duì)象發(fā)送給spider處理,常用屬性有(url, status, headers, body, request, meta, )

詳細(xì)介紹參考官網(wǎng)

看下面這個(gè)例子

from scrapy import Spider
from scrapy import Request

class TestSpider(Spider):
    name = 'test'
    start_urls = [
        "http://www.qq.com/",
    ]

    def login_parse(self, response):
        ''' 如果登錄成功,手動(dòng)構(gòu)造請(qǐng)求Request迭代返回 '''
        print response
        for i in range(0, 10):
            yield Request('http://www.example.com/list/1?page={0}'.format(i))

    def start_requests(self):
        ''' 覆蓋默認(rèn)的方法(忽略start_urls),返回登錄請(qǐng)求頁(yè),制定處理函數(shù)為login_parse '''
        return Request('http://www.example.com/login', method="POST" body='username=bomo&pwd=123456', callback=self.login_parse)


    def parse(self, response):
        ''' 默認(rèn)請(qǐng)求處理函數(shù) '''
        print response

四汹押、Selector

上面我們只是爬取了網(wǎng)頁(yè)的html文本矿筝,對(duì)于爬蟲,我們需要明確我們需要爬取的結(jié)構(gòu)化數(shù)據(jù)棚贾,需要對(duì)原文本進(jìn)行解析窖维,解析的方法通常有下面這些

  • 普通文本操作
  • 正則表達(dá)式:re
  • Dom樹操作:BeautifulSoup
  • XPath選擇器:lxml

scrapy默認(rèn)支持選擇器的功能,自帶的選擇器構(gòu)建與lxml之上妙痹,并對(duì)其進(jìn)行了改進(jìn)铸史,使用起來(lái)更為簡(jiǎn)潔明了

1. XPath選擇器

XPpath是標(biāo)準(zhǔn)的XML文檔查詢語(yǔ)言,可以用于查詢XML文檔中的節(jié)點(diǎn)和內(nèi)容细诸,關(guān)于XPath語(yǔ)法沛贪,可以參見(jiàn)這里

先看一個(gè)例子,通過(guò)html或xml構(gòu)造Selector對(duì)象震贵,然后通過(guò)xpath查詢節(jié)點(diǎn)利赋,并解析出節(jié)點(diǎn)的內(nèi)容

from scrapy import Selector

html = '<html><body><span>good</span><span>buy</span></body></html>'
sel = Selector(text=html)
nodes = sel.xpath('//span')
for node in nodes:
    print node.extract()

Selector相當(dāng)于節(jié)點(diǎn),通過(guò)xpath去到子節(jié)點(diǎn)集合(SelectorList)猩系,可以繼續(xù)搜索媚送,通過(guò)extract方法可以取出節(jié)點(diǎn)的值,extract方法也可以作用于SelectorList寇甸,對(duì)于SelectorList可以通過(guò)extract_first取出第一個(gè)節(jié)點(diǎn)的值

  • 通過(guò)text()取出節(jié)點(diǎn)的內(nèi)容
  • 通過(guò)@href去除節(jié)點(diǎn)屬性值(這里是取出href屬性的值)
  • 直接對(duì)節(jié)點(diǎn)取值塘偎,則是輸出節(jié)點(diǎn)的字符串

2. CSS選擇器

除了XPath選擇器,scrapy還支持css選擇器

html = """
        <html>
            <body>
                <span>good</span>
                <span>buy</span>
                <ul>
                    <li class="video_part_lists">aa<li>
                    <li class="video_part_lists">bb<li>
                    <li class="audio_part_lists">cc<li>
                    <li class="video_part_lists">
                        <a href="/">主頁(yè)</a>
                    <li>
                </ul>
            </body>
        </html>
        """
sel = Selector(text=html)

# 選擇class為video_part_lists的li節(jié)點(diǎn)
lis = sel.css('li.video_part_lists')

for li in lis:
    # 選擇a節(jié)點(diǎn)的屬性
    print li.css('a::attr(href)').extract()

關(guān)于css選擇器更多的規(guī)則拿霉,可以見(jiàn)w3c官網(wǎng)

五吟秩、Item類

上面我們只是爬取了網(wǎng)頁(yè)的html文本,對(duì)于爬蟲绽淘,我們需要明確我們需要爬取的結(jié)構(gòu)化數(shù)據(jù)涵防,我們定義一個(gè)item存儲(chǔ)分類信息,scrapy的item繼承自scrapy.Item

from scrapy import Item, Field

class DmozItem(Item):
    title = Field()
    link = Field()
    desc = Field()

scrapy.Item的用法與python中的字典用法基本一樣沪铭,只是做了一些安全限制壮池,屬性定義使用Field偏瓤,這里只是進(jìn)行了聲明,而不是真正的屬性椰憋,使用的時(shí)候通過(guò)鍵值對(duì)操作厅克,不支持屬性訪問(wèn)

what, 好坑爹,這意味著所有的屬性賦值都得用字符串了橙依,這里有解釋(還是沒(méi)太明白)

修改DmozSpider的parse方法

class DmozSpider(scrapy.Spider):
    ...
    def parse(self, response):
        for sel in response.xpath('//ul/li'):
            dmoz_item = DmozItem()
            dmoz_item['title'] = sel.xpath('a/text()').extract()
            dmoz_item['link'] = sel.xpath('a/@href').extract()
            dmoz_item['desc'] = sel.xpath('text()').extract()
            print dmoz_item

六证舟、Pipeline

spider負(fù)責(zé)爬蟲的配置,item負(fù)責(zé)聲明結(jié)構(gòu)化數(shù)據(jù)票编,而對(duì)于數(shù)據(jù)的處理褪储,在scrapy中使用管道的方式進(jìn)行處理,只要注冊(cè)過(guò)的管道都可以處理item數(shù)據(jù)(處理慧域,過(guò)濾鲤竹,保存)

下面看看管道的聲明方式,這里定義一個(gè)預(yù)處理管道PretreatmentPipeline.py昔榴,如果item的title為None辛藻,則設(shè)置為空字符串

class PretreatmentPipeline(object):
    def process_item(self, item, spider):
        if item['title']:
            # 不讓title為空
            item['title'] = ''
        return item

再定義一個(gè)過(guò)濾重復(fù)數(shù)據(jù)的管道DuplicatesPipeline.py,當(dāng)link重復(fù)互订,則丟棄

from scrapy.exceptions import DropItem

class DuplicatesPipeline(object):
    def __init__(self):
        self.links = set()

    def process_item(self, item, spider):
        if item['link'] in self.links:
            # 跑出DropItem表示丟掉數(shù)據(jù)
            raise DropItem("Duplicate item found: %s" % item)
        else:
            self.links.add(item['link'])
            return item

最后可以定義一個(gè)保存數(shù)據(jù)的管道吱肌,可以把數(shù)據(jù)保存到數(shù)據(jù)庫(kù)中

from scrapy.exceptions import DropItem
from Database import Database

class DatabasePipeline(object):
    def __init__(self):
        self.db = Database

    def process_item(self, item, spider):
        if self.db.item_exists(item['id']):
            self.db.update_item(item)
        else:
            self.db.insert_item(item)

定義好管道之后我們需要配置到爬蟲上,我們?cè)?code>settings.py模塊中配置仰禽,后面的數(shù)字表示管道的順序

ITEM_PIPELINES = {
    'pipelines.DuplicatesPipeline.DuplicatesPipeline': 1,
    'pipelines.PretreatmentPipeline.PretreatmentPipeline': 2,
}

我們也可以為spider配置單獨(dú)的pipeline

class TestSpider(Spider):
    # 自定義配置
    custom_settings = {
        # item處理管道
        'ITEM_PIPELINES': {
            'tutorial.pipelines.FangDetailPipeline.FangDetailPipeline': 1,
        },
    }
    ...

除了process_item方法外氮墨,pipeline還有open_spiderspider_closed兩個(gè)方法,在爬蟲啟動(dòng)和關(guān)閉的時(shí)候調(diào)用

七吐葵、Rule

爬蟲的通常需要在一個(gè)網(wǎng)頁(yè)里面爬去其他的鏈接规揪,然后一層一層往下爬,scrapy提供了LinkExtractor類用于對(duì)網(wǎng)頁(yè)鏈接的提取温峭,使用LinkExtractor需要使用CrawlSpider爬蟲類中猛铅,CrawlSpiderSpider相比主要是多了rules,可以添加一些規(guī)則凤藏,先看下面這個(gè)例子奸忽,爬取鏈家網(wǎng)的鏈接

from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor

class LianjiaSpider(CrawlSpider):
    name = "lianjia"

    allowed_domains = ["lianjia.com"]

    start_urls = [
        "http://bj.lianjia.com/ershoufang/"
    ]

    rules = [
        # 匹配正則表達(dá)式,處理下一頁(yè)
        Rule(LinkExtractor(allow=(r'http://bj.lianjia.com/ershoufang/pg\s+$',)), callback='parse_item'),

        # 匹配正則表達(dá)式,結(jié)果加到url列表中,設(shè)置請(qǐng)求預(yù)處理函數(shù)
        # Rule(FangLinkExtractor(allow=('http://www.lianjia.com/client/', )), follow=True, process_request='add_cookie')
    ]

    def parse_item(self, response):
        # 這里與之前的parse方法一樣,處理
        pass

1. Rule對(duì)象

Role對(duì)象有下面參數(shù)

  • link_extractor:鏈接提取規(guī)則
  • callback:link_extractor提取的鏈接的請(qǐng)求結(jié)果的回調(diào)
  • cb_kwargs:附加參數(shù)揖庄,可以在回調(diào)函數(shù)中獲取到
  • follow:表示提取的鏈接請(qǐng)求完成后是否還要應(yīng)用當(dāng)前規(guī)則(boolean)栗菜,如果為False則不會(huì)對(duì)提取出來(lái)的網(wǎng)頁(yè)進(jìn)行進(jìn)一步提取,默認(rèn)為False
  • process_links:處理所有的鏈接的回調(diào)蹄梢,用于處理從response提取的links疙筹,通常用于過(guò)濾(參數(shù)為link列表)
  • process_request:鏈接請(qǐng)求預(yù)處理(添加header或cookie等)

2. LinkExtractor

LinkExtractor常用的參數(shù)有:

  • allow:提取滿足正則表達(dá)式的鏈接
  • deny:排除正則表達(dá)式匹配的鏈接(優(yōu)先級(jí)高于allow
  • allow_domains:允許的域名(可以是strlist
  • deny_domains:排除的域名(可以是strlist
  • restrict_xpaths:提取滿足XPath選擇條件的鏈接(可以是strlist
  • restrict_css:提取滿足css選擇條件的鏈接(可以是strlist
  • tags:提取指定標(biāo)簽下的鏈接,默認(rèn)從aarea中提取(可以是strlist
  • attrs:提取滿足擁有屬性的鏈接腌歉,默認(rèn)為href(類型為list
  • unique:鏈接是否去重(類型為boolean
  • process_value:值處理函數(shù)(優(yōu)先級(jí)大于allow

關(guān)于LinkExtractor的詳細(xì)參數(shù)介紹見(jiàn)官網(wǎng)

注意:如果使用rules規(guī)則,請(qǐng)不要覆蓋或重寫CrawlSpiderparse方法齐苛,否則規(guī)則會(huì)失效翘盖,可以使用parse_start_urls方法

八、Middleware

從最開始的流程圖可以看到凹蜂,爬去一個(gè)資源鏈接的流程馍驯,首先我們配置spider相關(guān)的爬取信息,在啟動(dòng)爬取實(shí)例后玛痊,scrapy_engine從Spider取出Request(經(jīng)過(guò)SpiderMiddleware)汰瘫,然后丟給Scheduler(經(jīng)過(guò)SchedulerMiddleware),Scheduler接著把請(qǐng)求丟給Downloader(經(jīng)過(guò)DownloadMiddlware)擂煞,Downloader把請(qǐng)求結(jié)果丟還給Spider混弥,然后Spider把分析好的結(jié)構(gòu)化數(shù)據(jù)丟給Pipeline,Pipeline進(jìn)行分析保存或丟棄对省,這里面有4個(gè)角色

scrapy有下面三種middlewares

  • SpiderMiddleware:通常用于配置爬蟲相關(guān)的屬性蝗拿,引用鏈接設(shè)置,Url長(zhǎng)度限制蒿涎,成功狀態(tài)碼設(shè)置哀托,爬取深度設(shè)置,爬去優(yōu)先級(jí)設(shè)置等
  • DownloadMiddlware:通常用于處理下載之前的預(yù)處理劳秋,如請(qǐng)求Header(Cookie,User-Agent)仓手,登錄驗(yàn)證處理,重定向處理玻淑,代理服務(wù)器處理嗽冒,超時(shí)處理,重試處理等
  • SchedulerMiddleware(已經(jīng)廢棄):為了簡(jiǎn)化框架岁忘,調(diào)度器中間件已經(jīng)被廢棄辛慰,使用另外兩個(gè)中間件已經(jīng)夠用了

1. SpiderMiddleware

爬蟲中間件有下面幾個(gè)方法

  • process_spider_input:當(dāng)response通過(guò)spider的時(shí)候被調(diào)用,返回None(繼續(xù)給其他中間件處理)或拋出異常(不會(huì)給其他中間件處理干像,當(dāng)成異常處理)
  • process_spider_output:當(dāng)spider有item或Request輸出的時(shí)候調(diào)動(dòng)
  • process_spider_exception:處理出現(xiàn)異常時(shí)調(diào)用
  • process_start_requests:spider當(dāng)開始請(qǐng)求Request的時(shí)候調(diào)用

下面是scrapy自帶的一些中間件(在scrapy.spidermiddlewares命名空間下)

  • UrlLengthMiddleware
  • RefererMiddleware
  • OffsiteMiddleware
  • HttpErrorMiddleware
  • DepthMiddleware

我們自己實(shí)現(xiàn)一個(gè)SpiderMiddleware

TODO

參考鏈接:http://doc.scrapy.org/en/latest/topics/spider-middleware.html

2. DownloaderMiddleware

下載中間件有下面幾個(gè)方法

  • process_request:請(qǐng)求通過(guò)下載器的時(shí)候調(diào)用
  • process_response:請(qǐng)求完成后調(diào)用
  • process_exception:請(qǐng)求發(fā)生異常時(shí)調(diào)用
  • from_crawler:從crawler構(gòu)造的時(shí)候調(diào)用
  • from_settings:從settings構(gòu)造的時(shí)候調(diào)用
  • ``

更多詳細(xì)的參數(shù)解釋見(jiàn)這里

在爬取網(wǎng)頁(yè)的時(shí)候帅腌,使用不同的User-Agent可以提高請(qǐng)求的隨機(jī)性,定義一個(gè)隨機(jī)設(shè)置User-Agent的中間件RandomUserAgentMiddleware

import random

class RandomUserAgentMiddleware(object):
    """Randomly rotate user agents based on a list of predefined ones"""

    def __init__(self, agents):
        self.agents = agents

    # 從crawler構(gòu)造麻汰,USER_AGENTS定義在crawler的配置的設(shè)置中
    @classmethod
    def from_crawler(cls, crawler):
        return cls(crawler.settings.getlist('USER_AGENTS'))

    # 從settings構(gòu)造速客,USER_AGENTS定義在settings.py中
    @classmethod
    def from_settings(cls, settings):
        return cls(settings.getlist('USER_AGENTS'))

    def process_request(self, request, spider):
        # 設(shè)置隨機(jī)的User-Agent
        request.headers.setdefault('User-Agent', random.choice(self.agents))

settings.py設(shè)置USER_AGENTS參數(shù)

USER_AGENTS = [
    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; AcooBrowser; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
    "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Acoo Browser; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506)",
    "Mozilla/4.0 (compatible; MSIE 7.0; AOL 9.5; AOLBuild 4337.35; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
    "Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)",
    "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)",
    "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)",
    "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)",
    "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)",
    "Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6",
    "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1",
    "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0",
    "Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5",
    "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.8) Gecko Fedora/1.9.0.8-1.fc10 Kazehakase/0.5.6",
    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.20 (KHTML, like Gecko) Chrome/19.0.1036.7 Safari/535.20",
    "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168 Version/11.52",
]

配置爬蟲中間件的方式與pipeline類似,第二個(gè)參數(shù)表示優(yōu)先級(jí)

# 配置爬蟲中間件
SPIDER_MIDDLEWARES = {
    'myproject.middlewares.CustomSpiderMiddleware': 543,
    # 如果想禁用默認(rèn)的中間件的話五鲫,可以設(shè)置其優(yōu)先級(jí)為None
    'scrapy.spidermiddlewares.offsite.OffsiteMiddleware': None,
}

# 配置下載中間件
DOWNLOADER_MIDDLEWARES = {
    'myproject.middlewares.RandomUserAgentMiddleware': 543,
    'scrapy.contrib.downloadermiddleware.useragent.UserAgentMiddleware': None,
}

3. 代理服務(wù)器

爬蟲最怕的就是封ip溺职,這時(shí)候就需要代理服務(wù)器來(lái)爬取,scrapy設(shè)置代理服務(wù)器非常簡(jiǎn)單,只需要在請(qǐng)求前設(shè)置Request對(duì)象的meta屬性浪耘,添加proxy值即可乱灵,通常我們可以通過(guò)中間件來(lái)做

class ProxyMiddleware(object):
    def process_request(self, request, spider):
        proxy = 'https://178.33.6.236:3128'     # 代理服務(wù)器
        request.meta['proxy'] = proxy

九、緩存

scrapy默認(rèn)已經(jīng)自帶了緩存的功能七冲,通常我們只需要配置即可痛倚,打開settings.py

# 打開緩存
HTTPCACHE_ENABLED = True

# 設(shè)置緩存過(guò)期時(shí)間(單位:秒)
#HTTPCACHE_EXPIRATION_SECS = 0

# 緩存路徑(默認(rèn)為:.scrapy/httpcache)
HTTPCACHE_DIR = 'httpcache'

# 忽略的狀態(tài)碼
HTTPCACHE_IGNORE_HTTP_CODES = []

# 緩存模式(文件緩存)
HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'

更多參數(shù)參見(jiàn)這里

十、多線程

scrapy網(wǎng)絡(luò)請(qǐng)求是基于Twisted澜躺,而Twisted默認(rèn)支持多線程蝉稳,而且scrapy默認(rèn)也是通過(guò)多線程請(qǐng)求的,并且支持多核CPU的并發(fā)掘鄙,通常只需要配置一些參數(shù)即可

# 默認(rèn)Item并發(fā)數(shù):100
CONCURRENT_ITEMS = 100

# 默認(rèn)Request并發(fā)數(shù):16
CONCURRENT_REQUESTS = 16

# 默認(rèn)每個(gè)域名的并發(fā)數(shù):8
CONCURRENT_REQUESTS_PER_DOMAIN = 8

# 每個(gè)IP的最大并發(fā)數(shù):0表示忽略
CONCURRENT_REQUESTS_PER_IP = 0

更多參數(shù)參見(jiàn)這里

十一耘戚、常見(jiàn)問(wèn)題

1. 項(xiàng)目名稱問(wèn)題

在使用的時(shí)候遇到過(guò)一個(gè)問(wèn)題,在初始化scrapy startproject tutorial的時(shí)候操漠,如果使用了一些特殊的名字收津,如:test, fang等單詞的話,通過(guò)get_project_settings方法獲取配置的時(shí)候會(huì)出錯(cuò)颅夺,改成tutorial或一些復(fù)雜的名字的時(shí)候不會(huì)

ImportError: No module named tutorial.settings

這是一個(gè)bug朋截,在github上有提到:https://github.com/scrapy/scrapy/issues/428,但貌似沒(méi)有完全修復(fù)吧黄,修改一下名字就好了(當(dāng)然scrapy.cfgsettings.py里面也需要修改)

2. 為每個(gè)pipeline配置spider

上面我們是在settings.py里面配置pipeline部服,這里的配置的pipeline會(huì)作用于所有的spider,我們可以為每一個(gè)spider配置不同的pipeline拗慨,設(shè)置Spidercustom_settings對(duì)象

class LianjiaSpider(CrawlSpider):
    ...
    # 自定義配置
    custom_settings = {
        'ITEM_PIPELINES': {
            'tutorial.pipelines.TestPipeline.TestPipeline': 1,
        }
    }

3. 獲取提取鏈接的節(jié)點(diǎn)信息

通過(guò)LinkExtractor提取的scrapy.Link默認(rèn)不帶節(jié)點(diǎn)信息廓八,有時(shí)候我們需要節(jié)點(diǎn)的其他attribute屬性,scrapy.Link有個(gè)text屬性保存從節(jié)點(diǎn)提取的text值赵抢,我們可以通過(guò)修改lxmlhtml._collect_string_content變量為etree.tostring剧蹂,這樣可以在提取節(jié)點(diǎn)值就變味渲染節(jié)點(diǎn)scrapy.Link.text,然后根據(jù)scrapy.Link.text屬性拿到節(jié)點(diǎn)的html烦却,最后提取出我們需要的值

from lxml import etree
import scrapy.linkextractors.lxmlhtml
scrapy.linkextractors.lxmlhtml._collect_string_content = etree.tostring

4. 從數(shù)據(jù)庫(kù)中讀取urls

有時(shí)候我們已經(jīng)把urls下載到數(shù)據(jù)庫(kù)了宠叼,而不是在start_urls里配置,這時(shí)候可以重載spider的start_requests方法

def start_requests(self):
    for u in self.db.session.query(User.link):
        yield Request(u.link)

我們還可以在Request添加元數(shù)據(jù)其爵,然后在response中訪問(wèn)

def start_requests(self):
    for u in self.db.session.query(User):
        yield Request(u.link, meta={'name': u.name})

def parse(self, response):
    print response.url, response.meta['name']

5. 如何進(jìn)行循環(huán)爬取

有時(shí)候我們需要爬取的一些經(jīng)常更新的頁(yè)面冒冬,例如:間隔時(shí)間為2s,爬去一個(gè)列表前10頁(yè)的數(shù)據(jù)摩渺,從第一頁(yè)開始爬简烤,爬完成后重新回到第一頁(yè)

目前的思路是,通過(guò)parse方法迭代返回Request進(jìn)行增量爬取摇幻,由于scrapy默認(rèn)由緩存機(jī)制横侦,需要修改

6. 關(guān)于去重

scrapy默認(rèn)有自己的去重機(jī)制挥萌,默認(rèn)使用scrapy.dupefilters.RFPDupeFilter類進(jìn)行去重,主要邏輯如下

if include_headers:
    include_headers = tuple(to_bytes(h.lower())
                             for h in sorted(include_headers))
cache = _fingerprint_cache.setdefault(request, {})
if include_headers not in cache:
    fp = hashlib.sha1()
    fp.update(to_bytes(request.method))
    fp.update(to_bytes(canonicalize_url(request.url)))
    fp.update(request.body or b'')
    if include_headers:
        for hdr in include_headers:
            if hdr in request.headers:
                fp.update(hdr)
                for v in request.headers.getlist(hdr):
                    fp.update(v)
    cache[include_headers] = fp.hexdigest()
return cache[include_headers]

默認(rèn)的去重指紋是sha1(method + url + body + header)枉侧,這種方式并不能過(guò)濾很多引瀑,例如有一些請(qǐng)求會(huì)加上時(shí)間戳的,基本每次都會(huì)不同榨馁,這時(shí)候我們需要自定義過(guò)濾規(guī)則

from scrapy.dupefilter import RFPDupeFilter

class CustomURLFilter(RFPDupeFilter):
    """ 只根據(jù)url去重"""

    def __init__(self, path=None):
        self.urls_seen = set()
        RFPDupeFilter.__init__(self, path)

    def request_seen(self, request):
        if request.url in self.urls_seen:
            return True
        else:
            self.urls_seen.add(request.url)

配置setting

DUPEFILTER_CLASS = 'tutorial.custom_filters.CustomURLFilter'

7. 如何在Pipeline中處理不同的Item

scrapy所有的迭代出來(lái)的的Item都會(huì)經(jīng)過(guò)所有的Pipeline伤疙,如果需要處理不同的Item,只能通過(guò)isinstance()方法進(jìn)行類型判斷辆影,然后分別進(jìn)行處理,暫時(shí)沒(méi)有更好的方案

8. url按順序執(zhí)行

我們可以通過(guò)Request的priority控制url的請(qǐng)求的執(zhí)行順序黍特,但由于網(wǎng)絡(luò)請(qǐng)求的不確定性蛙讥,不能保證返回也是按照順序進(jìn)行的,如果需要進(jìn)行逐個(gè)url請(qǐng)求的話灭衷,吧url列表放在meta對(duì)象里面次慢,在response的時(shí)候迭代返回下一個(gè)Request對(duì)象到調(diào)度器,達(dá)到順序執(zhí)行的目的翔曲,暫時(shí)沒(méi)有更好的方案

十二迫像、總結(jié)

scrapy雖然是最有名的python爬蟲框架,但是還是有很多不足瞳遍,例如闻妓,item不能單獨(dú)配置給制定的pipeline,每一個(gè)爬取的所有item都會(huì)走遍所有的管道掠械,需要在管道里面去判斷不同類型的item由缆,如果在pipelines和items比較多的項(xiàng)目,將會(huì)讓項(xiàng)目變得非常臃腫

如有問(wèn)題歡迎到我的博客留言

十三猾蒂、參考鏈接

最后安利一下自己的博客:https://zhengbomo.github.io

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末均唉,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子肚菠,更是在濱河造成了極大的恐慌舔箭,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,366評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蚊逢,死亡現(xiàn)場(chǎng)離奇詭異层扶,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)时捌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門怒医,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人奢讨,你說(shuō)我怎么就攤上這事稚叹⊙姹。” “怎么了?”我有些...
    開封第一講書人閱讀 165,689評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵扒袖,是天一觀的道長(zhǎng)塞茅。 經(jīng)常有香客問(wèn)我,道長(zhǎng)季率,這世上最難降的妖魔是什么野瘦? 我笑而不...
    開封第一講書人閱讀 58,925評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮飒泻,結(jié)果婚禮上鞭光,老公的妹妹穿的比我還像新娘。我一直安慰自己泞遗,他們只是感情好惰许,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著史辙,像睡著了一般汹买。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上聊倔,一...
    開封第一講書人閱讀 51,727評(píng)論 1 305
  • 那天晦毙,我揣著相機(jī)與錄音,去河邊找鬼耙蔑。 笑死见妒,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的甸陌。 我是一名探鬼主播徐鹤,決...
    沈念sama閱讀 40,447評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼邀层!你這毒婦竟也來(lái)了返敬?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,349評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤寥院,失蹤者是張志新(化名)和其女友劉穎劲赠,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體秸谢,經(jīng)...
    沈念sama閱讀 45,820評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡凛澎,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了估蹄。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片塑煎。...
    茶點(diǎn)故事閱讀 40,127評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖臭蚁,靈堂內(nèi)的尸體忽然破棺而出最铁,到底是詐尸還是另有隱情讯赏,我是刑警寧澤,帶...
    沈念sama閱讀 35,812評(píng)論 5 346
  • 正文 年R本政府宣布冷尉,位于F島的核電站漱挎,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏雀哨。R本人自食惡果不足惜磕谅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望雾棺。 院中可真熱鬧膊夹,春花似錦、人聲如沸捌浩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)新锈。三九已至捐祠,卻和暖如春拓诸,著一層夾襖步出監(jiān)牢的瞬間侵佃,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工奠支, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留馋辈,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,388評(píng)論 3 373
  • 正文 我出身青樓倍谜,卻偏偏與公主長(zhǎng)得像迈螟,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子尔崔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容