Scrapy爬蟲入門教程四 Spider(爬蟲)
目錄
Spider
爬蟲是定義如何抓取某個網站(或一組網站)的類,包括如何執行抓取(即關注連結)以及如何從其網頁中提取結構化資料(即抓取專案)。換句話說,Spider是您定義用於為特定網站(或在某些情況下,一組網站)抓取和解析網頁的自定義行為的位置。
對於爬蟲,迴圈經歷這樣的事情:
-
您首先生成用於抓取第一個URL的初始請求,然後指定要使用從這些請求下載的響應呼叫的回撥函式。
第一個執行的請求通過呼叫 start_requests()(預設情況下)Request為在start_urls和中指定的URL生成的parse方法獲取, 並且該方法作為請求的回撥函式。
-
在回撥函式中,您將解析響應(網頁),並返回帶有提取的資料,Item物件, Request物件或這些物件的可迭代的物件。這些請求還將包含回撥(可能是相同的),然後由Scrapy下載,然後由指定的回撥處理它們的響應。
-
在回撥函式中,您通常使用選擇器來解析頁面內容 (但您也可以使用BeautifulSoup,lxml或您喜歡的任何機制),並使用解析的資料生成專案。
- 最後,從爬蟲返回的專案通常將持久儲存到資料庫(在某些專案管道中)或使用Feed匯出寫入檔案。
即使這個迴圈(或多或少)適用於任何種類的爬蟲,有不同種類的預設爬蟲捆綁到Scrapy中用於不同的目的。我們將在這裡談論這些型別。
class scrapy.spiders.Spider
這是最簡單的爬蟲,每個其他爬蟲必須繼承的爬蟲(包括與Scrapy捆綁在一起的爬蟲,以及你自己寫的爬蟲)。它不提供任何特殊功能。它只是提供了一個預設start_requests()
實現,它從start_urlsspider
屬性發送請求,並parse
為每個結果響應呼叫spider
的方法。
name
定義此爬蟲名稱的字串。爬蟲名稱是爬蟲如何由Scrapy定位(和例項化),因此它必須是唯一的。但是,沒有什麼能阻止你例項化同一個爬蟲的多個例項。這是最重要的爬蟲屬性,它是必需的。
如果爬蟲抓取單個域名,通常的做法是在域後面命名爬蟲。因此,例如,抓取的爬蟲mywebsite.com通常會被呼叫 mywebsite。
注意
在Python 2中,這必須是ASCII。
allowed_domains
允許此爬蟲抓取的域的字串的可選列表,指定一個列表可以抓取,其它就不會抓取了。
start_urls
當沒有指定特定網址時,爬蟲將開始抓取的網址列表。
custom_settings
執行此爬蟲時將從專案寬配置覆蓋的設定字典。它必須定義為類屬性,因為設定在例項化之前更新。
有關可用內建設定的列表,請參閱: 內建設定參考。
crawler
此屬性from_crawler()在初始化類後由類方法設定,並連結Crawler到此爬蟲例項繫結到的物件。
Crawlers在專案中封裝了很多元件,用於單個條目訪問(例如擴充套件,中介軟體,訊號管理器等)。有關詳情,請參閱抓取工具API。
settings
執行此爬蟲的配置。這是一個 Settings例項,有關此主題的詳細介紹,請參閱設定主題。
logger
用Spider建立的Python記錄器name
。您可以使用它通過它傳送日誌訊息,如記錄爬蟲程式中所述。
from_crawler
(crawler,* args,** kwargs )
是Scrapy用來建立爬蟲的類方法。
您可能不需要直接覆蓋這一點,因為預設實現充當方法的代理,__init__()
使用給定的引數args和命名引數kwargs呼叫它。
儘管如此,此方法 在新例項中設定crawler和settings屬性,以便以後可以在爬蟲程式中訪問它們。
- 引數:
- crawler(Crawlerinstance) - 爬蟲將繫結到的爬蟲
- args(list) - 傳遞給init()方法的引數
- kwargs(dict) - 傳遞給init()方法的關鍵字引數
start_requests()
此方法必須返回一個可迭代的第一個請求來抓取這個爬蟲。
有了start_requests(),就不寫了start_urls,寫了也沒有用。
預設實現是:start_urls,但是可以複寫的方法start_requests。
例如,如果您需要通過使用POST請求登入來啟動,您可以:
class MySpider(scrapy.Spider):
name = 'myspider'
def start_requests(self):
return [scrapy.FormRequest("http://www.example.com/login",
formdata={'user': 'john', 'pass': 'secret'},
callback=self.logged_in)]
def logged_in(self, response):
# here you would extract links to follow and return Requests for
# each of them, with another callback
pass
make_requests_from_url(url)
一種接收URL並返回Request 物件(或Request物件列表)進行抓取的方法。此方法用於在方法中構造初始請求 start_requests(),並且通常用於將URL轉換為請求。
除非重寫,此方法返回具有方法的Requests parse() 作為它們的回撥函式,並啟用dont_filter引數(Request有關更多資訊,請參閱類)。
parse(response)
這是Scrapy用於處理下載的響應的預設回撥,當它們的請求沒有指定回撥時。
該parse方法負責處理響應並返回所抓取的資料或更多的URL。其他請求回撥具有與Spider類相同的要求。
此方法以及任何其他請求回撥必須返回一個可迭代的Request和dicts或Item物件。
- 引數:
- response(Response) - 解析的響應
log(message[, level, component])
包裝器通過爬蟲傳送日誌訊息logger,保持向後相容性。有關詳細資訊,請參閱 從Spider記錄。
closed(reason)
當爬蟲關閉時召喚。此方法為spider_closed訊號的signals.connect()提供了一個快捷方式。
讓我們看一個例子:
import scrapy
class MySpider(scrapy.Spider):
name = 'example.com'
allowed_domains = ['jianshu.com']
start_urls = [
'http://www.jianshu.com/p/cc8d69ed5601',
'http://www.jianshu.com/p/41322417e0be',
'http://www.jianshu.com/p/6e8b207cf833',
]
def parse(self, response):
self.logger.info('A response from %s just arrived!', response.url)
從單個回撥中返回多個請求和項:
import scrapy
class MySpider(scrapy.Spider):
name = 'example.com'
allowed_domains = ['example.com']
start_urls = [
'http://www.example.com/1.html',
'http://www.example.com/2.html',
'http://www.example.com/3.html',
]
def parse(self, response):
for h3 in response.xpath('//h3').extract():
yield {"title": h3}
for url in response.xpath('//a/@href').extract():
yield scrapy.Request(url, callback=self.parse)
你可以直接使用start_requests(),而不是start_urls; 專案可以更加方便獲取資料:
import scrapy
from myproject.items import MyItem
class MySpider(scrapy.Spider):
name = 'example.com'
allowed_domains = ['example.com']
def start_requests(self):
yield scrapy.Request('http://www.example.com/1.html', self.parse)
yield scrapy.Request('http://www.example.com/2.html', self.parse)
yield scrapy.Request('http://www.example.com/3.html', self.parse)
def parse(self, response):
for h3 in response.xpath('//h3').extract():
yield MyItem(title=h3)
for url in response.xpath('//a/@href').extract():
yield scrapy.Request(url, callback=self.parse)
Spider arguments
爬蟲可以接收修改其行為的引數。爬蟲引數的一些常見用法是定義起始URL或將爬網限制到網站的某些部分,但它們可用於配置爬蟲的任何功能。
Spider crawl引數使用該-a選項通過命令 傳遞。例如:
scrapy crawl myspider -a category=electronics
爬蟲可以在他們的init方法中訪問引數:
import scrapy
class MySpider(scrapy.Spider):
name = 'myspider'
def __init__(self, category=None, *args, **kwargs):
super(MySpider, self).__init__(*args, **kwargs)
self.start_urls = ['http://www.example.com/categories/%s' % category]
# ...
預設的init方法將獲取任何爬蟲引數,並將它們作為屬性複製到爬蟲。上面的例子也可以寫成如下:
import scrapy
class MySpider(scrapy.Spider):
name = 'myspider'
def start_requests(self):
yield scrapy.Request('http://www.example.com/categories/%s' % self.category)
請記住,spider引數只是字串。爬蟲不會自己做任何解析。如果要從命令列設定start_urls屬性,則必須將它自己解析為列表,使用像 ast.literal_eval 或json.loads之類的屬性 ,然後將其設定為屬性。否則,你會導致迭代一個start_urls字串(一個非常常見的python陷阱),導致每個字元被看作一個單獨的url。
有效的用例是設定使用的http驗證憑據HttpAuthMiddleware 或使用者代理使用的使用者代理UserAgentMiddleware: scrapy crawl myspider -a http_user=myuser -a http_pass=mypassword -a user_agent=mybot
Spider引數也可以通過Scrapyd schedule.jsonAPI 傳遞。請參閱Scrapyd文件。
通用爬蟲
Scrapy附帶一些有用的通用爬蟲,你可以使用它來子類化你的爬蟲。他們的目的是為一些常見的抓取案例提供方便的功能,例如根據某些規則檢視網站上的所有連結,從站點地圖抓取或解析XML / CSV Feed。
對於在以下爬蟲中使用的示例,我們假設您有一個TestItem
在myproject.items
模組中宣告的專案:
import scrapy
class TestItem(scrapy.Item):
id = scrapy.Field()
name = scrapy.Field()
description = scrapy.Field()
CrawlSpider
類 scrapy.spiders.CrawlSpider
這是最常用的爬行常規網站的爬蟲,因為它通過定義一組規則為下列連結提供了一種方便的機制。它可能不是最適合您的特定網站或專案,但它是足夠通用的幾種情況,所以你可以從它開始,根據需要覆蓋更多的自定義功能,或只是實現自己的爬蟲。
CrawlSpider這個爬蟲還暴露了可覆蓋的方法:
parse_start_url(response)
對於start_urls響應呼叫此方法。它允許解析初始響應,並且必須返回Item
物件,Request
物件或包含任何物件的迭代器。
通過命令建立CrawlSpider
scrapy genspider -t crawl tencent tencent.com
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:滿足括號中“正則表示式”的值會被提取,如果為空,則全部匹配。
- deny:與這個正則表示式(或正則表示式列表)不匹配的URL一定不提取。
- allow_domains:會被提取的連結的domains。
- deny_domains:一定不會被提取連結的domains。
- restrict_xpaths:使用xpath表示式,和allow共同作用過濾連結。
除了從Spider繼承的屬性(你必須指定),這個類支援一個新的屬性:
Rules:
在rules中包含一個或多個Rule物件,每個Rule對爬取網站的動作定義了特定操作。如果多個rule匹配了相同的連結,則根據規則在本集合中被定義的順序,第一個會被使用。
Rule:抓取規則
class scrapy.spiders.Rule(link_extractor,callback = None,cb_kwargs = None,follow = None,process_links = None,process_request = None )
link_extractor是一個連結提取程式物件,它定義如何從每個爬網頁面提取連結。
class scrapy.spiders.Rule(
link_extractor,
callback = None,
cb_kwargs = None,
follow = None,
process_links = None,
process_request = None
)
- link_extractor:是一個Link Extractor物件,用於定義需要提取的連結。
- callback: 從link_extractor中每獲取到連結時,引數所指定的值作為回撥函式,該回調函式接受一個response作為其第一個引數。注意:當編寫爬蟲規則時,避免使用parse作為回撥函式。由於CrawlSpider使用parse方法來實現其邏輯,如果覆蓋了 parse方法,crawl spider將會執行失敗。
- cb_kwargs 是包含要傳遞給回撥函式的關鍵字引數的dict。
- follow:是一個布林(boolean)值,指定了根據該規則從response提取的連結是否需要跟進。 如果callback為None,follow 預設設定為True ,否則預設為False。
- process_links:指定該spider中哪個的函式將會被呼叫,從link_extractor中獲取到連結列表時將會呼叫該函式。該方法主要用來過濾。
- process_request:指定該spider中哪個的函式將會被呼叫, 該規則提取到每個request時都會呼叫該函式。 (用來過濾request)
CrawlSpider抓取爬蟲示例
現在讓我們來看一個CrawlSpider的例子:
import scrapy
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor
class MySpider(CrawlSpider):
name = 'example.com'
allowed_domains = ['example.com']
start_urls = ['http://www.example.com']
rules = (
# Extract links matching 'category.php' (but not matching 'subsection.php')
# and follow links from them (since no callback means follow=True by default).
Rule(LinkExtractor(allow=('category\.php', ), deny=('subsection\.php', ))),
# Extract links matching 'item.php' and parse them with the spider's method parse_item
Rule(LinkExtractor(allow=('item\.php', )), callback='parse_item'),
)
def parse_item(self, response):
self.logger.info('Hi, this is an item page! %s', response.url)
item = scrapy.Item()
item['id'] = response.xpath('//td[@id="item_id"]/text()').re(r'ID: (\d+)')
item['name'] = response.xpath('//td[@id="item_name"]/text()').extract()
item['description'] = response.xpath('//td[@id="item_description"]/text()').extract()
return item
這個爬蟲會開始抓取example.com的主頁,收集類別連結和項鍊接,用parse_item方法解析後者。對於每個專案響應,將使用XPath從HTML中提取一些資料,並將Item使用它填充。
XMLFeedSpider
class scrapy.spiders.XMLFeedSpider
XMLFeedSpider設計用於通過以特定節點名稱迭代XML訂閱源來解析XML訂閱源。迭代器可以選自:iternodes,xml和html。iternodes為了效能原因,建議使用迭代器,因為xml和迭代器html一次生成整個DOM為了解析它。但是,html當使用壞標記解析XML時,使用作為迭代器可能很有用。
要設定迭代器和標記名稱,必須定義以下類屬性:
-
iterator
定義要使用的迭代器的字串。它可以是:'iternodes'
- 基於正則表示式的快速迭代器'html'
- 使用的迭代器Selector。請記住,這使用DOM解析,並且必須載入所有DOM在記憶體中,這可能是一個大飼料的問題'xml'
- 使用的迭代器Selector。請記住,這使用DOM解析,並且必須載入所有DOM在記憶體中,這可能是一個大飼料的問題
它預設為:'iternodes'
。
itertag
一個具有要迭代的節點(或元素)的名稱的字串。示例: itertag = 'product'
namespaces
定義該文件中將使用此爬蟲處理的名稱空間的元組列表。在 與將用於自動註冊使用的名稱空間 的方法。(prefix, uri)prefixuriregister_namespace()
然後,您可以在屬性中指定具有名稱空間的itertag 節點。
例:
class YourSpider(XMLFeedSpider):
namespaces = [('n', 'http://www.sitemaps.org/schemas/sitemap/0.9')]
itertag = 'n:url'
# ...
除了這些新的屬性,這個爬蟲也有以下可重寫的方法:
adapt_response(response)
一種在爬蟲開始解析響應之前,在響應從爬蟲中介軟體到達時立即接收的方法。它可以用於在解析之前修改響應主體。此方法接收響應並返回響應(它可以是相同的或另一個)。
parse_node(response, selector)
對於與提供的標記名稱(itertag)匹配的節點,將呼叫此方法。接收Selector每個節點的響應和 。覆蓋此方法是必需的。否則,你的爬蟲將不工作。此方法必須返回一個Item物件,一個 Request物件或包含任何物件的迭代器。
process_results(response, results)
對於由爬蟲返回的每個結果(Items or Requests),將呼叫此方法,並且它將在將結果返回到框架核心之前執行所需的任何最後處理,例如設定專案ID。它接收結果列表和產生那些結果的響應。它必須返回結果列表(Items or Requests)。
XMLFeedSpider示例
這些爬蟲很容易使用,讓我們看一個例子:
from scrapy.spiders import XMLFeedSpider
from myproject.items import TestItem
class MySpider(XMLFeedSpider):
name = 'example.com'
allowed_domains = ['example.com']
start_urls = ['http://www.example.com/feed.xml']
iterator = 'iternodes' # This is actually unnecessary, since it's the default value
itertag = 'item'
def parse_node(self, response, node):
self.logger.info('Hi, this is a <%s> node!: %s', self.itertag, ''.join(node.extract()))
item = TestItem()
item['id'] = node.xpath('@id').extract()
item['name'] = node.xpath('name').extract()
item['description'] = node.xpath('description').extract()
return item
基本上我們做的是建立一個爬蟲,從給定的下載一個start_urls,然後遍歷每個item標籤,打印出來,並存儲一些隨機資料Item。
CSVFeedSpider
class scrapy.spiders.CSVF
這個爬蟲非常類似於XMLFeedSpider,除了它迭代行,而不是節點。在每次迭代中呼叫的方法是parse_row()。
delimiter
CSV檔案中每個欄位的帶分隔符的字串預設為’,’(逗號)。
quotechar
CSV檔案中每個欄位的包含字元的字串預設為’”’(引號)。
headers
檔案CSV Feed中包含的行的列表,用於從中提取欄位。
parse_row(response, row)
使用CSV檔案的每個提供(或檢測到)標頭的鍵接收響應和dict(表示每行)。這個爬蟲還給予機會重寫adapt_response和process_results方法的前和後處理的目的。
CSVFeedSpider示例
讓我們看一個類似於前一個例子,但使用 CSVFeedSpider:
from scrapy.spiders import CSVFeedSpider
from myproject.items import TestItem
class MySpider(CSVFeedSpider):
name = 'example.com'
allowed_domains = ['example.com']
start_urls = ['http://www.example.com/feed.csv']
delimiter = ';'
quotechar = "'"
headers = ['id', 'name', 'description']
def parse_row(self, response, row):
self.logger.info('Hi, this is a row!: %r', row)
item = TestItem()
item['id'] = row['id']
item['name'] = row['name']
item['description'] = row['description']
return item
SitemapSpider
class scrapy.spiders.SitemapSpider
SitemapSpider允許您通過使用Sitemaps發現網址來抓取網站。
它支援巢狀Sitemap和從robots.txt發現Sitemap網址 。
sitemap_urls
指向您要抓取的網址的網站的網址列表。
您還可以指向robots.txt,它會解析為從中提取Sitemap網址。
sitemap_rules
元組列表其中:(regex, callback)
- regex是與從Sitemap中提取的網址相匹配的正則表示式。 regex可以是一個str或一個編譯的正則表示式物件。
- callback是用於處理與正則表示式匹配的url的回撥。callback可以是字串(指示蜘蛛方法的名稱)或可呼叫的。
例如: sitemap_rules = [('/product/', 'parse_product')]
規則按順序應用,只有匹配的第一個將被使用。
如果省略此屬性,則會在parse回撥中處理在站點地圖中找到的所有網址。
sitemap_follow
應遵循的網站地圖的正則表示式列表。這隻適用於使用指向其他Sitemap檔案的Sitemap索引檔案的網站。
預設情況下,將跟蹤所有網站地圖。
sitemap_alternate_links
指定是否url應遵循一個備用連結。這些是在同一個url塊中傳遞的另一種語言的同一網站的連結。
例如:
<url>
<loc>http://example.com/</loc>
<xhtml:link rel="alternate" hreflang="de" href="http://example.com/de"/>
</url>
使用sitemap_alternate_linksset
,這將檢索兩個URL。隨著 sitemap_alternate_links
禁用,只有http://example.com/將進行檢索。
預設為sitemap_alternate_links
禁用。
SitemapSpider示例
最簡單的示例:使用parse回撥處理通過站點地圖發現的所有網址 :
from scrapy.spiders import SitemapSpider
class MySpider(SitemapSpider):
sitemap_urls = ['http://www.example.com/sitemap.xml']
def parse(self, response):
pass # ... scrape item here ...
使用某個回撥處理一些網址,並使用不同的回撥處理其他網址:
from scrapy.spiders import SitemapSpider
class MySpider(SitemapSpider):
sitemap_urls = ['http://www.example.com/sitemap.xml']
sitemap_rules = [
('/product/', 'parse_product'),
('/category/', 'parse_category'),
]
def parse_product(self, response):
pass # ... scrape product ...
def parse_category(self, response):
pass # ... scrape category ...
關注robots.txt檔案中定義的sitemaps,並且只跟蹤其網址包含/sitemap_shop
以下內容的Sitemap :
from scrapy.spiders import SitemapSpider
class MySpider(SitemapSpider):
sitemap_urls = ['http://www.example.com/robots.txt']
sitemap_rules = [
('/shop/', 'parse_shop'),
]
sitemap_follow = ['/sitemap_shops']
def parse_shop(self, response):
pass # ... scrape shop here ...
將SitemapSpider與其他來源網址結合使用:
from scrapy.spiders import SitemapSpider
class MySpider(SitemapSpider):
sitemap_urls = ['http://www.example.com/robots.txt']
sitemap_rules = [
('/shop/', 'parse_shop'),
]
other_urls = ['http://www.example.com/about']
def start_requests(self):
requests = list(super(MySpider, self).start_requests())
requests += [scrapy.Request(x, self.parse_other) for x in self.other_urls]
return requests
def parse_shop(self, response):
pass # ... scrape shop here ...
def parse_other(self, response):
pass # ... scrape other here ...