1 parse()方法的工作機制:
1. 因為使用的yield,而不是return。parse函數将會被當做一個生成器使用。scrapy會逐一擷取parse方法中生成的結果,并判斷該結果是一個什麼樣的類型;
2. 如果是request則加入爬取隊列,如果是item類型則使用pipeline處理,其他類型則傳回錯誤資訊。
3. scrapy取到第一部分的request不會立馬就去發送這個request,隻是把這個request放到隊列裡,然後接着從生成器裡擷取;
4. 取盡第一部分的request,然後再擷取第二部分的item,取到item了,就會放到對應的pipeline裡處理;
5. parse()方法作為回調函數(callback)指派給了Request,指定parse()方法來處理這些請求 scrapy.Request(url, callback=self.parse)
6. Request對象經過排程,執行生成 scrapy.http.response()的響應對象,并送回給parse()方法,直到排程器中沒有Request(遞歸的思路)
7. 取盡之後,parse()工作結束,引擎再根據隊列和pipelines中的内容去執行相應的操作;
8. 程式在取得各個頁面的items前,會先處理完之前所有的request隊列裡的請求,然後再提取items。
7. 這一切的一切,Scrapy引擎和排程器将負責到底。
2 CrawlSpiders:定義了一些規則跟進link
通過下面的指令可以快速建立 CrawlSpider模闆 的代碼: scrapy genspider -t crawl tencent tencent.com
上一個案例中,我們通過正規表達式,制作了新的url作為Request請求參數,現在我們可以換個花樣…
class scrapy.spiders.CrawlSpider
它是Spider的派生類,Spider類的設計原則是隻爬取start_url清單中的網頁,而CrawlSpider類定義了一些規則(rule)來提供跟進link的友善的機制,從爬取的網頁中擷取link并繼續爬取的工作更适合。
源碼參考
class CrawlSpider(Spider):
rules = ()
def __init__(self, *a, **kw):
super(CrawlSpider, self).__init__(*a, **kw)
self._compile_rules()
#首先調用parse()來處理start_urls中傳回的response對象
#parse()則将這些response對象傳遞給了_parse_response()函數處理,并設定回調函數為parse_start_url()
#設定了跟進标志位follow = True
#parse将傳回item和跟進了的Request對象
def parse(self, response):
return self._parse_response(response, self.parse_start_url, cb_kwargs={}, follow=True)
#處理start_url中傳回的response,需要重寫
def parse_start_url(self, response):
return []
def process_results(self, response, results):
return results
#從response中抽取符合任一使用者定義'規則'的連結,并構造成Resquest對象傳回
def _requests_to_follow(self, response):
if not isinstance(response, HtmlResponse):
return
seen = set()
#抽取之内的所有連結,隻要通過任意一個'規則',即表示合法
for n, rule in enumerate(self._rules):
links = [l for l in rule.link_extractor.extract_links(response) if l not in seen]
#使用使用者指定的process_links處理每個連接配接
if links and rule.process_links:
links = rule.process_links(links)
#将連結加入seen集合,為每個連結生成Request對象,并設定回調函數為_repsonse_downloaded()
for link in links:
seen.add(link)
#構造Request對象,并将Rule規則中定義的回調函數作為這個Request對象的回調函數
r = Request(url=link.url, callback=self._response_downloaded)
r.meta.update(rule=n, link_text=link.text)
#對每個Request調用process_request()函數。
#該函數預設為indentify,即不做任何處理,直接傳回該Request.
yield rule.process_request(r)
#處理通過rule提取出的連接配接,并傳回item以及request
def _response_downloaded(self, response):
rule = self._rules[response.meta['rule']]
return self._parse_response(response, rule.callback, rule.cb_kwargs, rule.follow)
#解析response對象,會用callback解析處理他,并傳回request或Item對象
def _parse_response(self, response, callback, cb_kwargs, follow=True):
#首先判斷是否設定了回調函數 (該回調函數可能是rule中的解析函數,也可能是 parse_start_url函數)
#如果設定了回調函數(parse_start_url()),那麼首先用parse_start_url()處理response對象,
#然後再交給process_results處理。傳回cb_res的一個清單
if callback:
#如果是parse調用的,則會解析成Request對象
#如果是rule callback,則會解析成Item
cb_res = callback(response, **cb_kwargs) or ()
cb_res = self.process_results(response, cb_res)
for requests_or_item in iterate_spider_output(cb_res):
yield requests_or_item
#如果需要跟進,那麼使用定義的Rule規則提取并傳回這些Request對象
if follow and self._follow_links:
#傳回每個Request對象
for request_or_item in self._requests_to_follow(response):
yield request_or_item
def _compile_rules(self):
def get_method(method):
if callable(method):
return method
elif isinstance(method, basestring):
return getattr(self, method, None)
self._rules = [copy.copy(r) for r in self.rules]
for rule in self._rules:
rule.callback = get_method(rule.callback)
rule.process_links = get_method(rule.process_links)
rule.process_request = get_method(rule.process_request)
def set_crawler(self, crawler):
super(CrawlSpider, self).set_crawler(crawler)
self._follow_links = crawler.settings.getbool('CRAWLSPIDER_FOLLOW_LINKS', True)
CrawlSpider繼承于Spider類,除了繼承過來的屬性外(name、allow_domains),還提供了新的屬性和方法:
3 LinkExtractors
class scrapy.linkextractors.LinkExtractor
Link Extractors 的目的很簡單: 提取連結。
每個LinkExtractor有唯一的公共方法是 extract_links(),它接收一個 Response 對象,并傳回一個 scrapy.link.Link 對象。
Link Extractors要執行個體化一次,并且 extract_links 方法會根據不同的 response 調用多次提取連結。
class scrapy.linkextractors.LinkExtractor(
allow = (),
deny = (),
allow_domains = (),
deny_domains = (),
deny_extensions = None,
restrict_xpaths = (),
tags = ('a','area'),
attrs = ('href'),
canonicalize = True,
unique = True,
process_value = None
)
主要參數:
-
:滿足括号中“正規表達式”的值會被提取,如果為空,則全部比對。allow
-
:與這個正規表達式(或正規表達式清單)比對的URL一定不提取。deny
-
:會被提取的連結的domains。allow_domains
-
:一定不會被提取連結的domains。deny_domains
-
:使用xpath表達式,和allow共同作用過濾連結。restrict_xpaths
4 rules:适合全站爬取
在rules中包含一個或多個Rule對象,每個Rule對爬取網站的動作定義了特定操作。如果多個rule比對了相同的連結,則根據規則在本集合中被定義的順序,第一個會被使用。
class scrapy.spiders.Rule(
link_extractor,
callback = None,
cb_kwargs = None,
follow = None,
process_links = None,
process_request = None
)
-
:是一個Link Extractor對象,用于定義需要提取的連結。link_extractor
-
: 從link_extractor中每擷取到連結時,參數所指定的值作為回調函數,該回調函數接受一個response作為其第一個參數。callback
注意:當編寫爬蟲規則時,避免使用parse作為回調函數。由于CrawlSpider使用parse方法來實作其邏輯,如果覆寫了 parse方法,crawl spider将會運作失敗。
-
:是一個布爾(boolean)值,指定了根據該規則從response提取的連結是否需要跟進。 如果callback為None,follow 預設設定為True ,否則預設為False。follow
-
:指定該spider中哪個的函數将會被調用,從link_extractor中擷取到連結清單時将會調用該函數。該方法主要用來過濾。process_links
-
:指定該spider中哪個的函數将會被調用, 該規則提取到每個request時都會調用該函數。 (用來過濾request)process_request
5 爬取規則(Crawling rules)
繼續用騰訊招聘為例,給出配合rule使用CrawlSpider的例子:
- 首先運作
scrapy shell "http://hr.tencent.com/position.php?&start=0#a"
- 導入LinkExtractor,建立LinkExtractor執行個體對象。:
page_lx = LinkExtractor(allow=(‘position.php?&start=\d+’))from scrapy.linkextractors import LinkExtractor
> allow : LinkExtractor對象最重要的參數之一,這是一個正規表達式或正規表達式清單,必須要比對這個正規表達式(或正規表達式清單)的URL才會被提取,如果沒有給出(或為空), 它會比對所有的連結。
> deny : 用法同allow,隻不過與這個正規表達式比對的URL不會被提取)。它的優先級高于 allow 的參數,如果沒有給出(或None), 将不排除任何連結。
3. 調用LinkExtractor執行個體的extract_links()方法查詢比對結果:
page_lx.extract_links(response)
- 沒有查到:
[]
- 注意轉義字元的問題,繼續重新比對:
page_lx = LinkExtractor(allow=('position\.php\?&start=\d+')) # page_lx = LinkExtractor(allow = ('start=\d+')) page_lx.extract_links(response)
## CrawlSpider 版本
那麼,scrapy shell測試完成之後,修改以下代碼
```python
#提取比對 'http://hr.tencent.com/position.php?&start=\d+'的連結
page_lx = LinkExtractor(allow = ('start=\d+'))
rules = [
#提取比對,并使用spider的parse方法進行分析;并跟進連結(沒有callback意味着follow預設為True)
Rule(page_lx, callback = 'parse', follow = True)
]
這麼寫對嗎?
不對!千萬記住 callback 千萬不能寫 parse,再次強調:由于CrawlSpider使用parse方法來實作其邏輯,是以回調函數必須保證不能與CrawlSpider中parse方法重名 , 如果覆寫了 parse方法,crawl spider将會運作失敗。
# -*- coding: utf-8 -*-
import re
import scrapy
from scrapy.spiders import CrawlSpider, Rule # 提取超連結的規則
from scrapy.linkextractors import LinkExtractor # 提取超連結
from Tencent import items
class MytencentSpider(CrawlSpider):
name = 'myTencent'
allowed_domains = ['hr.tencent.com']
start_urls = ['https://hr.tencent.com/position.php?lid=2218&start=0#a']
page_lx = LinkExtractor(allow=("start=\d+"))
rules = [
Rule(page_lx, callback="parseContent", follow=True)
]
# parse(self, response)
def parseContent(self, response):
for data in response.xpath("//tr[@class=\"even\"] | //tr[@class=\"odd\"]"):
item = items.TencentItem()
item["jobTitle"] = data.xpath("./td[1]/a/text()")[0].extract()
item["jobLink"] = "https://hr.tencent.com/" + data.xpath("./td[1]/a/@href")[0].extract()
item["jobCategories"] = data.xpath("./td[1]/a/text()")[0].extract()
item["number"] = data.xpath("./td[2]/text()")[0].extract()
item["location"] = data.xpath("./td[3]/text()")[0].extract()
item["releasetime"] = data.xpath("./td[4]/text()")[0].extract()
yield item
# for i in range(1, 200):
# newurl = "https://hr.tencent.com/position.php?lid=2218&start=%d#a" % (i*10)
# yield scrapy.Request(newurl, callback=self.parse)
運作:
scrapy crawl tencent
6 robots協定
Robots協定(也稱為爬蟲協定、機器人協定等)的全稱是“網絡爬蟲排除标準”(Robots Exclusion Protocol),網站通過Robots協定告訴搜尋引擎哪些頁面可以抓取,哪些頁面不能抓取。robots.txt檔案是一個文本檔案。當一個搜尋蜘蛛通路一個
站點時,它會首先
檢查該站點
根目錄下是否存在robots.txt,如果存在,搜尋機器人就會按照該檔案中的内容來确定通路的範圍;如果該檔案不存在,所有的搜尋蜘蛛将能夠通路網站上所有沒有被密碼保護的頁面。
User-agent: * 這裡的*代表的所有的搜尋引擎種類,*是一個通配符
Disallow: /admin/ 這裡定義是禁止爬尋admin目錄下面的目錄
Disallow: /require/ 這裡定義是禁止爬尋require目錄下面的目錄
Disallow: /ABC/ 這裡定義是禁止爬尋ABC目錄下面的目錄
Disallow: /cgi-bin/*.htm 禁止通路/cgi-bin/目錄下的所有以".htm"為字尾的URL(包含子目錄)。
Disallow: /*?* 禁止通路網站中所有包含問号 (?) 的網址
Disallow: /.jpg$ 禁止抓取網頁所有的.jpg格式的圖檔
Disallow:/ab/adc.html 禁止爬取ab檔案夾下面的adc.html檔案。
Allow: /cgi-bin/ 這裡定義是允許爬尋cgi-bin目錄下面的目錄
Allow: /tmp 這裡定義是允許爬尋tmp的整個目錄
Allow: .htm$ 僅允許通路以".htm"為字尾的URL。
Allow: .gif$ 允許抓取網頁和gif格式圖檔
Sitemap: 網站地圖 告訴爬蟲這個頁面是網站地圖
執行個體分析:淘寶網的 robots.txt檔案
禁止robots協定将 ROBOTSTXT_OBEY = True改為False
7 Logging
Scrapy提供了log功能,可以通過 logging 子產品使用。
可以修改配置檔案settings.py,任意位置添加下面兩行,效果會清爽很多。
LOG_ENABLED = True # 開啟
LOG_FILE = "TencentSpider.log" #日志檔案名
LOG_LEVEL = "INFO" #日志級别
Log levels
- Scrapy提供5層logging級别:
- CRITICAL - 嚴重錯誤(critical)
- ERROR - 一般錯誤(regular errors)
- WARNING - 警告資訊(warning messages)
- INFO - 一般資訊(informational messages)
- DEBUG - 調試資訊(debugging messages)
logging設定
通過在setting.py中進行以下設定可以被用來配置logging:
-
預設: True,啟用loggingLOG_ENABLED
-
預設: ‘utf-8’,logging使用的編碼LOG_ENCODING
-
預設: None,在目前目錄裡建立logging輸出檔案的檔案名LOG_FILE
-
預設: ‘DEBUG’,log的最低級别LOG_LEVEL
-
預設: False 如果為 True,程序所有的标準輸出(及錯誤)将會被重定向到log中。例如,執行 print “hello” ,其将會在Scrapy log中顯示。LOG_STDOUT
- 日志子產品已經被scrapy棄用,改用python自帶日志子產品
import logging
LOG_FORMAT = "%(asctime)s - %(levelname)s - %(message)s" # 設定輸出格式
DATE_FORMAT = "%Y/%m/%d %H:%M:%S" # 設定時間格式
logging.basicConfig(filename='tianya.log', filemode='a+', format=LOG_FORMAT, datefmt=DATE_FORMAT)
logging.warning('錯誤')
setting.py 設定抓取間隔
DOWNLOAD_DELAY = 0.25 #設定下載下傳間隔為250ms