scrapy是python最有名的爬蟲框架之一,可以很方便的進行web抓取,並且提供了很強的定製型,這裡記錄簡單學習的過程和在實際應用中會遇到的一些常見問題
一、安裝
在安裝scrapy之前有一些依賴需要安裝,否則可能會安裝失敗,scrapy的選擇器依賴於lxml
,還有Twisted
網路引擎,下面是ubuntu下安裝的過程
1. linux下安裝
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# 1. 安裝xml依賴庫 $ 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 |
2. Mac下安裝
1 2 3 4 5 |
# 安裝xml依賴庫 $ xcode-select —install # 其實相關依賴pip會自動幫我們裝上 $ pip install scrapy |
mac下安裝有時候會失敗,建議使用virtualenv
安裝在獨立的環境下,可以減少一些問題,因為mac系統自帶python,例如一些依賴庫依賴的一些新的版本,而升級新版本會把舊版本解除安裝掉,解除安裝可能會有許可權的問題
二、基本使用
1. 初始化scrapy專案
我們可以使用命令列初始化一個專案
1 |
$ scrapy startproject tutorial |
這裡可以檢視scrapy更多其他的命令
初始化完成後,我們得到下面目錄結構
1 2 3 4 5 6 |
scrapy.cfg: 專案的配置檔案 tutorial/: 該專案的python模組, 在這裡新增程式碼 items.py: 專案中的item檔案 pipelines.py: 專案中的pipelines檔案. settings.py: 專案全域性設定檔案. spiders/ 爬蟲模組目錄 |
我們先看一下scrapy的處理流程
scrapy由下面幾個部分組成
spiders
:爬蟲模組,負責配置需要爬取的資料和爬取規則,以及解析結構化資料items
:定義我們需要的結構化資料,使用相當於dict
pipelines
:管道模組,處理spider模組分析好的結構化資料,如儲存入庫等middlewares
:中介軟體,相當於鉤子,可以對爬取前後做預處理,如修改請求header,url過濾等
我們先來看一個例子,在spiders
目錄下新建一個模組DmozSpider.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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/" ] # 預設response處理函式 def parse(self, response): # 把結果寫到檔案中 filename = response.url.split("/")[-2] with open(filename, 'wb') as f: f.write(response.body) |
開啟終端進入根目錄,執行下面命令
1 |
$ scrapy crawl dmoz |
爬蟲開始爬取start_urls定義的url,並輸出到檔案中,最後輸出爬去報告,會輸出爬取得統計結果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
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) |
這裡我們完成了簡單的爬取和儲存的操作,會在根目錄生成兩個檔案Resources
和Books
2. 通過程式碼執行爬蟲
每次進入控制檯執行爬蟲還是比較麻煩的,而且不好除錯,我們可以通過CrawlerProcess
通過程式碼執行爬蟲,新建一個模組run.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
from scrapy.crawler import CrawlerProcess from scrapy.utils.project import get_project_settings from spiders.DmozSpider import DmozSpider # 獲取settings.py模組的設定 settings = get_project_settings() process = CrawlerProcess(settings=settings) # 可以新增多個spider # process.crawl(Spider1) # process.crawl(Spider2) process.crawl(DmozSpider) # 啟動爬蟲,會阻塞,直到爬取完成 process.start() |
參考:http://doc.scrapy.org/en/latest/topics/practices.html#run-scrapy-from-a-script
三、Scrapy類
如上面的DmozSpider
類,爬蟲類繼承自scrapy.Spider
,用於構造Request
物件給Scheduler
1. 常用屬性與方法
屬性
name
:爬蟲的名字,必須唯一(如果在控制檯使用的話,必須配置)start_urls
:爬蟲初始爬取的連結列表parse
:response結果處理函式custom_settings
:自定義配置,覆蓋settings.py
中的預設配置
方法
start_requests
:啟動爬蟲的時候呼叫,預設是呼叫make_requests_from_url
方法爬取start_urls
的連結,可以在這個方法裡面定製,如果重寫了該方法,start_urls預設將不會被使用,可以在這個方法裡面定製一些自定義的url,如登入,從資料庫讀取url等,本方法返回Request物件make_requests_from_url
:預設由start_requests
呼叫,可以配置Request物件,返回Request物件parse
:response到達spider的時候預設呼叫,如果在Request物件配置了callback函式,則不會呼叫,parse方法可以迭代返回Item
或Request
物件,如果返回Request物件,則會進行增量爬取
2. Request與Response物件
每個請求都是一個Request物件,Request物件定義了請求的相關資訊(url
, method
, headers
, body
, cookie
, priority
)和回撥的相關資訊(meta
, callback
, dont_filter
, errback
),通常由spider迭代返回
其中meta
相當於附加變數,可以在請求完成後通過response.meta
訪問
請求完成後,會通過Response
物件傳送給spider處理,常用屬性有(url
, status
, headers
, body
, request
, meta
, )
詳細介紹參考官網
- https://doc.scrapy.org/en/latest/topics/request-response.html#request-objects
- https://doc.scrapy.org/en/latest/topics/request-response.html#response-objects
看下面這個例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
from scrapy import Spider from scrapy import Request class TestSpider(Spider): name = 'test' start_urls = [ "http://www.qq.com/", ] def login_parse(self, response): ''' 如果登入成功,手動構造請求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): ''' 覆蓋預設的方法(忽略start_urls),返回登入請求頁,制定處理函式為login_parse ''' return Request('http://www.example.com/login', method="POST" body='username=bomo&pwd=123456', callback=self.login_parse) def parse(self, response): ''' 預設請求處理函式 ''' print response |
四、Selector
上面我們只是爬取了網頁的html文字,對於爬蟲,我們需要明確我們需要爬取的結構化資料,需要對原文字進行解析,解析的方法通常有下面這些
- 普通文字操作
- 正規表示式:
re
- Dom樹操作:
BeautifulSoup
- XPath選擇器:
lxml
scrapy預設支援選擇器的功能,自帶的選擇器構建與lxml之上,並對其進行了改進,使用起來更為簡潔明瞭
1. XPath選擇器
XPpath是標準的XML文件查詢語言,可以用於查詢XML文件中的節點和內容,關於XPath語法,可以參見這裡
先看一個例子,通過html或xml構造Selector物件,然後通過xpath查詢節點,並解析出節點的內容
1 2 3 4 5 6 7 |
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相當於節點,通過xpath去到子節點集合(SelectorList),可以繼續搜尋,通過extract
方法可以取出節點的值,extract
方法也可以作用於SelectorList,對於SelectorList可以通過extract_first
取出第一個節點的值
- 通過
text()
取出節點的內容 - 通過
@href
去除節點屬性值(這裡是取出href
屬性的值) - 直接對節點取值,則是輸出節點的字串
2. CSS選擇器
除了XPath選擇器,scrapy還支援css選擇器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
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="/">主頁</a> <li> </ul> </body> </html> """ sel = Selector(text=html) # 選擇class為video_part_lists的li節點 lis = sel.css('li.video_part_lists') for li in lis: # 選擇a節點的屬性 print li.css('a::attr(href)').extract() |
關於css選擇器更多的規則,可以見w3c官網
五、Item類
上面我們只是爬取了網頁的html文字,對於爬蟲,我們需要明確我們需要爬取的結構化資料,我們定義一個item儲存分類資訊,scrapy的item繼承自scrapy.Item
1 2 3 4 5 6 |
from scrapy import Item, Field class DmozItem(Item): title = Field() link = Field() desc = Field() |
scrapy.Item
的用法與python中的字典用法基本一樣,只是做了一些安全限制,屬性定義使用Field,這裡只是進行了宣告,而不是真正的屬性,使用的時候通過鍵值對操作,不支援屬性訪問
what, 好坑爹,這意味著所有的屬性賦值都得用字串了,這裡有解釋(還是沒太明白)
修改DmozSpider的parse方法
1 2 3 4 5 6 7 8 9 |
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負責爬蟲的配置,item負責宣告結構化資料,而對於資料的處理,在scrapy中使用管道的方式進行處理,只要註冊過的管道都可以處理item資料(處理,過濾,儲存)
下面看看管道的宣告方式,這裡定義一個預處理管道PretreatmentPipeline.py
,如果item的title為None,則設定為空字串
1 2 3 4 5 6 |
class PretreatmentPipeline(object): def process_item(self, item, spider): if item['title']: # 不讓title為空 item['title'] = '' return item |
再定義一個過濾重複資料的管道DuplicatesPipeline.py
,當link重複,則丟棄
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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表示丟掉資料 raise DropItem("Duplicate item found: %s" % item) else: self.links.add(item['link']) return item |
最後可以定義一個儲存資料的管道,可以把資料儲存到資料庫中
1 2 3 4 5 6 7 8 9 10 11 12 |
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) |
定義好管道之後我們需要配置到爬蟲上,我們在settings.py
模組中配置,後面的數字表示管道的順序
1 2 3 4 |
ITEM_PIPELINES = { 'pipelines.DuplicatesPipeline.DuplicatesPipeline': 1, 'pipelines.PretreatmentPipeline.PretreatmentPipeline': 2, } |
我們也可以為spider配置單獨的pipeline
1 2 3 4 5 6 7 8 9 |
class TestSpider(Spider): # 自定義配置 custom_settings = { # item處理管道 'ITEM_PIPELINES': { 'tutorial.pipelines.FangDetailPipeline.FangDetailPipeline': 1, }, } ... |
除了process_item
方法外,pipeline還有open_spider
和spider_closed
兩個方法,在爬蟲啟動和關閉的時候呼叫
七、Rule
爬蟲的通常需要在一個網頁裡面爬去其他的連結,然後一層一層往下爬,scrapy提供了LinkExtractor類用於對網頁連結的提取,使用LinkExtractor需要使用CrawlSpider
爬蟲類中,CrawlSpider
與Spider
相比主要是多了rules
,可以新增一些規則,先看下面這個例子,爬取鏈家網的連結
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
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 = [ # 匹配正規表示式,處理下一頁 Rule(LinkExtractor(allow=(r'http://bj.lianjia.com/ershoufang/pg\s+$',)), callback='parse_item'), # 匹配正規表示式,結果加到url列表中,設定請求預處理函式 # Rule(FangLinkExtractor(allow=('http://www.lianjia.com/client/', )), follow=True, process_request='add_cookie') ] def parse_item(self, response): # 這裡與之前的parse方法一樣,處理 pass |
1. Rule物件
Role物件有下面引數
link_extractor
:連結提取規則callback
:link_extractor提取的連結的請求結果的回撥cb_kwargs
:附加引數,可以在回撥函式中獲取到follow
:表示提取的連結請求完成後是否還要應用當前規則(boolean),如果為False
則不會對提取出來的網頁進行進一步提取,預設為Falseprocess_links
:處理所有的連結的回撥,用於處理從response提取的links,通常用於過濾(引數為link列表)process_request
:連結請求預處理(新增header或cookie等)
2. LinkExtractor
LinkExtractor常用的引數有:
allow
:提取滿足正規表示式的連結deny
:排除正規表示式匹配的連結(優先順序高於allow
)allow_domains
:允許的域名(可以是str
或list
)deny_domains
:排除的域名(可以是str
或list
)restrict_xpaths
:提取滿足XPath選擇條件的連結(可以是str
或list
)restrict_css
:提取滿足css選擇條件的連結(可以是str
或list
)tags
:提取指定標籤下的連結,預設從a
和area
中提取(可以是str
或list
)attrs
:提取滿足擁有屬性的連結,預設為href
(型別為list
)unique
:連結是否去重(型別為boolean
)process_value
:值處理函式(優先順序大於allow
)
關於LinkExtractor的詳細引數介紹見官網
注意:如果使用rules規則,請不要覆蓋或重寫
CrawlSpider
的parse
方法,否則規則會失效,可以使用parse_start_urls
方法
八、Middleware
從最開始的流程圖可以看到,爬去一個資源連結的流程,首先我們配置spider相關的爬取資訊,在啟動爬取例項後,scrapy_engine
從Spider取出Request
(經過SpiderMiddleware
),然後丟給Scheduler(經過SchedulerMiddleware
),Scheduler接著把請求丟給Downloader(經過DownloadMiddlware
),Downloader把請求結果丟還給Spider,然後Spider把分析好的結構化資料丟給Pipeline,Pipeline進行分析儲存或丟棄,這裡面有4個角色
scrapy有下面三種middlewares
SpiderMiddleware
:通常用於配置爬蟲相關的屬性,引用連結設定,Url長度限制,成功狀態碼設定,爬取深度設定,爬去優先順序設定等DownloadMiddlware
:通常用於處理下載之前的預處理,如請求Header(Cookie,User-Agent),登入驗證處理,重定向處理,代理伺服器處理,超時處理,重試處理等SchedulerMiddleware
(已經廢棄):為了簡化框架,排程器中介軟體已經被廢棄,使用另外兩個中介軟體已經夠用了
1. SpiderMiddleware
爬蟲中介軟體有下面幾個方法
process_spider_input
:當response通過spider的時候被呼叫,返回None(繼續給其他中介軟體處理)或丟擲異常(不會給其他中介軟體處理,當成異常處理)process_spider_output
:當spider有item或Request輸出的時候調動process_spider_exception
:處理出現異常時呼叫process_start_requests
:spider當開始請求Request的時候呼叫
下面是scrapy自帶的一些中介軟體(在scrapy.spidermiddlewares
名稱空間下)
- UrlLengthMiddleware
- RefererMiddleware
- OffsiteMiddleware
- HttpErrorMiddleware
- DepthMiddleware
我們自己實現一個SpiderMiddleware
1 |
TODO |
參考連結:http://doc.scrapy.org/en/latest/topics/spider-middleware.html
2. DownloaderMiddleware
下載中介軟體有下面幾個方法
process_request
:請求通過下載器的時候呼叫process_response
:請求完成後呼叫process_exception
:請求發生異常時呼叫from_crawler
:從crawler構造的時候呼叫from_settings
:從settings構造的時候呼叫
更多詳細的引數解釋見這裡
在爬取網頁的時候,使用不同的User-Agent
可以提高請求的隨機性,定義一個隨機設定User-Agent的中介軟體RandomUserAgentMiddleware
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import random class RandomUserAgentMiddleware(object): """Randomly rotate user agents based on a list of predefined ones""" def __init__(self, agents): self.agents = agents # 從crawler構造,USER_AGENTS定義在crawler的配置的設定中 @classmethod def from_crawler(cls, crawler): return cls(crawler.settings.getlist('USER_AGENTS')) # 從settings構造,USER_AGENTS定義在settings.py中 @classmethod def from_settings(cls, settings): return cls(settings.getlist('USER_AGENTS')) def process_request(self, request, spider): # 設定隨機的User-Agent request.headers.setdefault('User-Agent', random.choice(self.agents)) |
在settings.py
設定USER_AGENTS引數
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
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類似,第二個參數列示優先順序
1 2 3 4 5 6 7 8 9 10 11 12 |
# 配置爬蟲中介軟體 SPIDER_MIDDLEWARES = { 'myproject.middlewares.CustomSpiderMiddleware': 543, # 如果想禁用預設的中介軟體的話,可以設定其優先順序為None 'scrapy.spidermiddlewares.offsite.OffsiteMiddleware': None, } # 配置下載中介軟體 DOWNLOADER_MIDDLEWARES = { 'myproject.middlewares.RandomUserAgentMiddleware': 543, 'scrapy.contrib.downloadermiddleware.useragent.UserAgentMiddleware': None, } |
九、快取
scrapy預設已經自帶了快取的功能,通常我們只需要配置即可,開啟settings.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# 開啟快取 HTTPCACHE_ENABLED = True # 設定快取過期時間(單位:秒) #HTTPCACHE_EXPIRATION_SECS = 0 # 快取路徑(預設為:.scrapy/httpcache) HTTPCACHE_DIR = 'httpcache' # 忽略的狀態碼 HTTPCACHE_IGNORE_HTTP_CODES = [] # 快取模式(檔案快取) HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage' |
更多引數參見這裡
十、多執行緒
scrapy網路請求是基於Twisted,而Twisted預設支援多執行緒,而且scrapy預設也是通過多執行緒請求的,並且支援多核CPU的併發,通常只需要配置一些引數即可
1 2 3 4 5 6 7 8 9 10 11 |
# 預設Item併發數:100 CONCURRENT_ITEMS = 100 # 預設Request併發數:16 CONCURRENT_REQUESTS = 16 # 預設每個域名的併發數:8 CONCURRENT_REQUESTS_PER_DOMAIN = 8 # 每個IP的最大併發數:0表示忽略 CONCURRENT_REQUESTS_PER_IP = 0 |
更多引數參見這裡
十一、常見問題
1. 專案名稱問題
在使用的時候遇到過一個問題,在初始化scrapy startproject tutorial
的時候,如果使用了一些特殊的名字,如:test
, fang
等單詞的話,通過get_project_settings
方法獲取配置的時候會出錯,改成tutorial
或一些複雜的名字的時候不會
1 |
ImportError: No module named tutorial.settings |
這是一個bug,在github上有提到:https://github.com/scrapy/scrapy/issues/428,但貌似沒有完全修復,修改一下名字就好了(當然scrapy.cfg
和settings.py
裡面也需要修改)
2. 為每個pipeline配置spider
上面我們是在settings.py裡面配置pipeline,這裡的配置的pipeline會作用於所有的spider,我們可以為每一個spider配置不同的pipeline,設定Spider
的custom_settings
物件
1 2 3 4 5 6 7 8 |
class LianjiaSpider(CrawlSpider): ... # 自定義配置 custom_settings = { 'ITEM_PIPELINES': { 'tutorial.pipelines.TestPipeline.TestPipeline': 1, } } |
3. 獲取提取連結的節點資訊
通過LinkExtractor提取的scrapy.Link
預設不帶節點資訊,有時候我們需要節點的其他attribute屬性,scrapy.Link
有個text
屬性儲存從節點提取的text
值,我們可以通過修改lxmlhtml._collect_string_content
變數為etree.tostring
,這樣可以在提取節點值就變味渲染節點scrapy.Link.text
,然後根據scrapy.Link.text
屬性拿到節點的html,最後提取出我們需要的值
1 2 3 |
from lxml import etree import scrapy.linkextractors.lxmlhtml scrapy.linkextractors.lxmlhtml._collect_string_content = etree.tostring |
4. 從資料庫中讀取urls
有時候我們已經把urls下載到資料庫了,而不是在start_urls裡配置,這時候可以過載spider的start_requests
方法
1 2 3 |
def start_requests(self): for u in self.db.session.query(User.link): yield Request(u.link) |
我們還可以在Request新增後設資料,然後在response中訪問
1 2 3 4 5 6 |
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. 如何進行迴圈爬取
有時候我們需要爬取的一些經常更新的頁面,例如:間隔時間為2s,爬去一個列表前10頁的資料,從第一頁開始爬,爬完成後重新回到第一頁
目前的思路是,通過parse方法迭代返回Request進行增量爬取,由於scrapy預設由快取機制,需要修改
6. 關於去重
scrapy預設有自己的去重機制,預設使用scrapy.dupefilters.RFPDupeFilter
類進行去重,主要邏輯如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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] |
預設的去重指紋是sha1(method + url + body + header),這種方式並不能過濾很多,例如有一些請求會加上時間戳的,基本每次都會不同,這時候我們需要自定義過濾規則
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
from scrapy.dupefilter import RFPDupeFilter class CustomURLFilter(RFPDupeFilter): """ 只根據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
1 |
DUPEFILTER_CLASS = 'tutorial.custom_filters.CustomURLFilter' |
7. 如何在Pipeline中處理不同的Item
scrapy所有的迭代出來的的Item都會經過所有的Pipeline,如果需要處理不同的Item,只能通過isinstance()
方法進行型別判斷,然後分別進行處理,暫時沒有更好的方案
8. url按順序執行
我們可以通過Request的priority控制url的請求的執行順序,但由於網路請求的不確定性,不能保證返回也是按照順序進行的,如果需要進行逐個url請求的話,吧url列表放在meta物件裡面,在response的時候迭代返回下一個Request物件到排程器,達到順序執行的目的,暫時沒有更好的方案
十二、總結
scrapy雖然是最有名的python爬蟲框架,但是還是有很多不足,例如,item不能單獨配置給制定的pipeline,每一個爬取的所有item都會走遍所有的管道,需要在管道里面去判斷不同型別的item,如果在pipelines和items比較多的專案,將會讓專案變得非常臃腫
如有問題歡迎到我的部落格留言