CrawlSpider
是Scrapy
框架中一個非常實用的爬蟲基類,它繼承自Spider
類,主要用于實現基于規則的網頁爬取。相較于普通的Spider
類,CrawlSpider
可以根據預定義的規則自動跟進頁面中的鏈接,從而實現更高效、更靈活的爬取。
Scrapy 創建CrawlSpider
爬蟲
目標網址:http://quotes.toscrape.com/
目標:匹配top10標簽里面的所有quote
觀察其他的URL鏈接,這些都是干擾,我們只需要匹配top10里面的鏈接,所有需要編寫正則表達式來匹配
1.創建 Scrapy 項目:在命令行輸入scrapy startproject myproject
,這里的myproject
是項目名。
2.進入項目目錄:輸入cd myproject
。
3.創建 CrawlSpider:輸入scrapy genspider -t crawl myspider example.com
,myspider
是爬蟲名,example.com
是初始爬取的域名。
scrapy genspider -t crawl quotes quotes.toscrape.com
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Ruleclass QuotesSpider(CrawlSpider):name = "quotes"allowed_domains = ["quotes.toscrape.com"]start_urls = ["http://quotes.toscrape.com/"]rules = (Rule(LinkExtractor(allow=r'/tag/[a-z]+/$'), callback='parse_tag', follow=False),)def parse_tag(self, response):tag_url = response.urlprint(f"Extracted tag URL: {tag_url}")
rules
:是一個元組,包含一個或多個Rule
對象,每個Rule
對象定義了一個爬取規則。LinkExtractor(allow=r'/tag/[a-z]+/$')
:創建一個LinkExtractor
對象,使用正則表達式r'/tag/[a-z]+/$'
來提取符合規則的鏈接。該正則表達式的含義是:匹配含/tag/
的鏈接,后面跟著一個或多個小寫字母,最后以/
結尾的鏈接。callback='parse_tag'
:當LinkExtractor
提取到符合規則的鏈接并訪問該鏈接對應的頁面后,會調用parse_tag
方法來處理該頁面。follow=False
:表示不跟進從當前頁面提取的符合規則的鏈接。也就是說,爬蟲只會處理當前頁面中符合規則的鏈接,不會繼續深入這些鏈接對應的頁面去提取更多鏈接。(會自動訪問符合規則的鏈接)
可以看到爬取的就是top10的
相對 URL 和絕對 URL 的差異: Scrapy 的
LinkExtractor
在處理鏈接時,處理的是絕對 URL 而非 HTML 中的相對 URL。要是你的正則表達式是基于相對 URL 來寫的,就可能會匹配失敗。比如,HTML 里的相對 URL 是/tag/inspirational/
,但 Scrapy 處理時會把它變成絕對 URLhttp://quotes.toscrape.com/tag/inspirational/
因此r'^/tag/[a-z]+/$'
就無法匹配,因為絕對 URL 是以http://
開頭的,并非/tag/
。
或者改為這樣也是可以的r'^http://quotes.toscrape.com/tag/[a-z]+/$'
接下來我們繼續跟進,將follow=True
。進入這些標簽頁面進一步爬取詳情內容
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Ruleclass QuotesSpider(CrawlSpider):name = "quotes"allowed_domains = ["quotes.toscrape.com"]start_urls = ["http://quotes.toscrape.com/"]rules = (Rule(LinkExtractor(allow=r'/tag/[a-z]+/$'), callback='parse_tag', follow=True),)def parse_tag(self, response):# 打印當前頁面的URLtag_url = response.urlprint(f"Extracted tag URL: {tag_url}")# 提取名言和作者quotes = response.css('div.quote')for quote in quotes:text = quote.css('span.text::text').get()author = quote.css('small.author::text').get()print(f"Quote: {text}, Author: {author}")
翻頁邏輯
有的標簽類別不止一頁數據,例如
http://quotes.toscrape.com/tag/love/page/2/
可以看到我們只需要匹配next
里面鏈接,其他的為干擾。我們可以使用更精確的 CSS 選擇器來配合 LinkExtractor
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Ruleclass QuotesSpider(CrawlSpider):name = "quotes"allowed_domains = ["quotes.toscrape.com"]start_urls = ["http://quotes.toscrape.com/"]rules = (# 規則1:提取所有標簽鏈接Rule(LinkExtractor(allow=r'/tag/[a-z]+/$'), callback='parse_tag', follow=True),# 規則2:使用CSS選擇器提取<li>標簽下的分頁鏈接Rule(LinkExtractor(restrict_css='li.next a'), callback='parse_tag', follow=True),)def parse_tag(self, response):# 打印當前頁面的URLtag_url = response.urlprint(f"Extracted tag URL: {tag_url}")# 提取名言和作者quotes = response.css('div.quote')for quote in quotes:text = quote.css('span.text::text').get()author = quote.css('small.author::text').get()print(f"Quote: {text}, Author: {author}")
如果 Rule(LinkExtractor(restrict_css=‘li.next a’), callback=‘parse_tag’,
follow=False
)
那么爬蟲只會處理當前頁面中提取到的分頁鏈接對應的頁面,而不會進一步去跟進這些頁面中的其他分頁鏈接,所以只能獲取到第二頁的數據,無法獲取到第二頁之后的頁面數據。
還可以使用 XPath
提取
rules = (# 規則1:提取所有標簽鏈接Rule(LinkExtractor(allow=r'/tag/[a-z]+/$'), callback='parse_tag', follow=True),# 規則2:使用CSS選擇器提取<li>標簽下的分頁鏈接Rule(LinkExtractor(restrict_css='li.next a'), callback='parse_tag', follow=True),# 規則3:使用XPath提取<li>標簽下的分頁鏈接Rule(LinkExtractor(restrict_xpaths='//li[@class="next"]/a'), callback='parse_tag', follow=True),)
Scrapy
內部有一個鏈接去重機制,默認使用 scrapy.dupefilters.RFPDupeFilter
來過濾重復的請求。當 LinkExtractor
提取到鏈接后,Scrapy
會先檢查這個鏈接是否已經在請求隊列中或者已經被處理過,如果是,就不會再次發起請求。
規則 2 和規則 3 提取的是相同的,由于 Scrapy
的去重機制,相同的鏈接只會被請求和處理一次,所以不會因為規則 2 和規則 3 提取到相同的鏈接而導致 parse_tag
方法被重復調用并打印兩次數據。
雖然 Scrapy
會對鏈接進行去重,但如果你的 parse_tag
方法內部存在一些邏輯,可能會導致數據重復處理。例如,如果你在 parse_tag
方法中對數據進行了一些存儲操作,并且沒有進行去重處理,那么可能會出現數據重復存儲的情況
LinkExtractor allow參數
-
字符串列表:
allow=['/tag/love/', '/tag/humor/']
,LinkExtractor
會提取包含/tag/love/
或者/tag/humor/
的鏈接 -
編譯好的正則表達式對象:
tag_pattern = re.compile(r'/tag/[a-z]+/$') rules = (Rule(LinkExtractor(allow=tag_pattern), callback='parse_item', follow=True), )
-
空列表或空字符串:如果你傳入一個空列表
[]
或者空字符串''
,LinkExtractor
會提取頁面中的所有鏈接。