創(chuàng)建一個(gè)Scrapy項(xiàng)目
1.創(chuàng)建項(xiàng)目文件夾tutorial
scrapy startproject tutorial
2.創(chuàng)建Spider類
Spider的用法
使用命令行創(chuàng)建一個(gè)Spider
cd tutorial
scrapy genspider quotes quotes.toscrape.com
第一個(gè)參數(shù)是Spider的名稱绰筛,第二個(gè)參數(shù)是網(wǎng)站域名
執(zhí)行完畢后痴施,spider文件夾中多了一個(gè)quotes.py宇驾。
- name 項(xiàng)目的名字寺谤,用于區(qū)分不同的Spider
- allowed_domains,允許爬取的域名
- start_urls档押,它包含了Spider在啟動(dòng)時(shí)爬取的url列表逞力,初始請(qǐng)求是由它來(lái)定義的。
- parse倾哺,Spider的一個(gè)方法轧邪,負(fù)責(zé)解析下載start_urls里面的連接構(gòu)成的請(qǐng)求后返回的相應(yīng)、提取數(shù)據(jù)或者進(jìn)一步生成要處理的請(qǐng)求羞海。
# -*- coding: utf-8 -*-
import scrapy
from scrapyspider.items import QuoteItem
class QuotesSpider(scrapy.Spider):
name = 'quotes'
allowed_domains = ['quotes.toscrape.com']
start_urls = ['http://quotes.toscrape.com/']
def parse(self, response):
quotes = response.css('.quote')
for quote in quotes:
item = QuoteItem()
item['item'] = quote.css('.text::text').extract_first()
item['author'] = quote.css('.author::text').extract_first()
item['tags'] = quote.css('.tags::text').extract()
yield item
next = response.css('.pager .next a::attr(href)').extract_first()
url = response.urljoin(next)
yield scrapy.Request(url=url,callback=self.parse)
3.創(chuàng)建Item
Item是保存爬取數(shù)據(jù)的容器忌愚。
類似于字典+額外的保護(hù)機(jī)制,避免拼寫錯(cuò)誤或者定義字段錯(cuò)誤却邓。
創(chuàng)建時(shí)需要繼承scrapy.Item類硕糊,并且定義類型為scrapy.Field的字段。
# -*- coding: utf-8 -*-
# Define here the models for your scraped items
#
# See documentation in:
# https://doc.scrapy.org/en/latest/topics/items.html
import scrapy
class QuoteItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
text = scrapy.Field()
author = scrapy.Field()
tags = scrapy.Field()
4.解析Response&使用Item
在parse()方法中腊徙,直接對(duì)response變量包含的內(nèi)容進(jìn)行解析简十,解析**text、author撬腾、tags螟蝙。
def parse(self, response):
quotes = response.css('.quote')
for quote in quotes:
item = QuoteItem()
item['item'] = quote.css('.text::text').extract_first()
item['author'] = quote.css('.author::text').extract_first()
item['tags'] = quote.css('.tags::text').extract()
yield item
選擇器selector:CSS或XPath
Scrapy提供了兩個(gè)實(shí)用的快捷方法
response.xpath()和response.css()
二者功能完全等同于
response.selector.xpath()和response.selector.css()
CSS
這里使用CSS選擇器,給出CSS選擇器的基礎(chǔ)使用規(guī)則民傻。
選擇器 | 例子 | 含義 |
---|---|---|
.class | .aaa | 選擇class="aaa"的所有元素 |
#id | #firstname | 選擇 id="firstname" 的所有元素 |
* | 選擇所有元素 | |
element | p | 選擇所有<p>元素 |
element element | div p | 選擇 <div> 元素內(nèi)部的所有 <p> 元素 |
a[title] | 選取所有擁有title屬性的a元素 |
對(duì)于text:
extract_first()方法獲取結(jié)果的第一個(gè)元素胰默,可以避免空列表取[0]導(dǎo)致數(shù)組越界场斑。extract_first('Default Image')匹配不到結(jié)果時(shí)返回該參數(shù)Default Image
extract()方法獲取所有結(jié)果組成的列表
XPath
表達(dá)式 | 描述 |
---|---|
nodename | 選取此節(jié)點(diǎn)的所有子節(jié)點(diǎn) |
/ | 從當(dāng)前節(jié)點(diǎn)選取直接子節(jié)點(diǎn) |
// | 從當(dāng)前節(jié)點(diǎn)選取子孫節(jié)點(diǎn) |
. | 選取當(dāng)前節(jié)點(diǎn) |
.. | 選取當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn) |
@ | 選取屬性/屬性獲取 |
[ ] | 增加限制條件 |
text() | 文本獲取 |
屬性多值匹配-某些節(jié)點(diǎn)的某個(gè)屬性可能有多個(gè)值。
使用contains()函數(shù)牵署,第一個(gè)參數(shù)傳入屬性名稱漏隐,第二個(gè)參數(shù)傳入屬性值,只要此屬性包含所傳入的屬性值奴迅,就可以完成匹配了青责。
例如代碼:
from lxml import etree
text = '''
<li class="li li-first"><a href="link.html">first item</a></li>
html = etree.HTML(text)
result = html.xpath('//li[contains(@class,"li")]/a/text()')
print(result)
//運(yùn)行結(jié)果
['first item']
多屬性匹配-根據(jù)多個(gè)屬性確定一個(gè)節(jié)點(diǎn)
from lxml import etree
text = '''
<li class="li li-first" name="item"><a href="link.html">first item</a></li>
html = etree.HTML(text)
result = html.xpath('//li[contains(@class,"li") and @name="item"]/a/text()')
print(result)
//運(yùn)行結(jié)果
['first item']
正則匹配
response對(duì)象不能直接調(diào)用re()和re_first()方法。如果想要對(duì)全文進(jìn)行正則匹配半沽,可以先調(diào)用xpath()方法再正則匹配爽柒。
5.后續(xù)Request
上面操作實(shí)現(xiàn)從初始頁(yè)面抓取內(nèi)容。
- 我們需要從當(dāng)前頁(yè)面中找到信息來(lái)生成下一個(gè)請(qǐng)求
- 在下一個(gè)請(qǐng)求的頁(yè)面里找到信息再構(gòu)造再下一個(gè)請(qǐng)求者填。
- 這樣循環(huán)往復(fù)迭代浩村,從而實(shí)現(xiàn)整站的爬取。
基于按鈕的鏈接:http://quotes.toscrape.com/page/2
利用scrapy.Request構(gòu)造下一個(gè)請(qǐng)求
scrapy.Request兩個(gè)參數(shù)
- url 請(qǐng)求鏈接
- callback 回調(diào)函數(shù)占哟。當(dāng)指定了該回調(diào)函數(shù)的請(qǐng)求完成后心墅,獲取到響應(yīng),引擎會(huì)將該響應(yīng)作為參數(shù)傳遞給這個(gè)回調(diào)函數(shù)榨乎≡踉铮回調(diào)函數(shù)進(jìn)行解析或生成下一個(gè)請(qǐng)求,回調(diào)函數(shù)如上文的
parse()所示蜜暑。
由于每頁(yè)的結(jié)構(gòu)相同铐姚,可以再次使用parse()方法來(lái)做頁(yè)面解析。
next = response.css('.pager .next a::attr(href)').extract_first()
url = response.urljoin(next)
yield scrapy.Request(url=url,callback=self.parse)
- 獲取a超鏈接中的href屬性肛捍,這里用到了::attr(href)操作隐绵。然后再調(diào)用extract_first()方法獲取內(nèi)容。
- urljoin()方法拙毫,將相對(duì)URL構(gòu)造成一個(gè)絕對(duì)的URL(補(bǔ)全URL)
- 利用scrapy.Request構(gòu)造新請(qǐng)求
6.運(yùn)行
scrapy crawl quotes
這里運(yùn)行的時(shí)候有各種可能的原因?qū)е虏荒軓哪繕?biāo)獲取內(nèi)容
- 網(wǎng)址的錯(cuò)誤
- 格式錯(cuò)誤如縮進(jìn)問(wèn)題
- 網(wǎng)頁(yè)有反爬蟲(chóng)依许,
- 在settings文件中添加USER_AGENT
- 添加IP代理
- 改機(jī)器人協(xié)議以及cookie
- 設(shè)置延遲
- 分段函數(shù)中所要爬取的url有反爬蟲(chóng),可以添加print(requests.get(url).status_code)調(diào)試
7.保存到文件
Scrapy提供的Feed Exports可以輕松抓取結(jié)果輸出缀蹄。
#json
scrapy crawl quotes -o quotes.json
#jsonline 每一個(gè)Item輸出一行JSON
scrapy crawl quotes -o quotes.jl
scrapy crawl quotes -o quotes.csv
scrapy crawl quotes -o quotes.xml
scrapy crawl quotes -o quotes.pickle
scrapy crawl quotes -o quotes.marshal
#ftp 遠(yuǎn)程輸出
scrapy crawl quotes -o ftp://user::pass@ftp.example.com/path/to/quotes.csv
8.使用Item Pipeline(項(xiàng)目管道)
調(diào)用發(fā)生在Spider產(chǎn)生Item之后峭跳。
當(dāng)Spider解析完Response之后,Item就會(huì)傳遞到Item Pipeline缺前,被定義的Item Pipeline組件會(huì)順次調(diào)用蛀醉,完成一連串的處理過(guò)程,比如數(shù)據(jù)清洗诡延、存儲(chǔ)等滞欠。
主要功能有4個(gè):
- 清理HTML數(shù)據(jù)
- 驗(yàn)證爬取數(shù)據(jù),檢查爬取字段肆良。
- 查重并丟棄重復(fù)內(nèi)容筛璧。
- 將爬取結(jié)果保存到數(shù)據(jù)庫(kù)
將結(jié)果保存到MongoDB或者篩選Item。
核心方法
- 必須要實(shí)現(xiàn)的一個(gè)方法是:process_item(item,spider)
被定義的Item Pipeline會(huì)默認(rèn)調(diào)用這個(gè)方法對(duì)Item進(jìn)行處理惹恃。
返回:Item類型的值(此Item會(huì)被低優(yōu)先級(jí)的Item Pipeline的process_item()方法處理夭谤,直到所有的方法被調(diào)用完畢)或者拋出一個(gè)DropItem異常(丟棄該Item,不再進(jìn)行處理)巫糙。 - 實(shí)用方法
open_spider(self,spider)
在Spider開(kāi)啟的時(shí)候被自動(dòng)調(diào)用朗儒,可以在這里做一些初始化操作,如開(kāi)啟數(shù)據(jù)庫(kù)連接等参淹。 - 實(shí)用方法
close_spider(spider)
在Spider關(guān)閉時(shí)自動(dòng)調(diào)用醉锄,可以在這里做一些收尾工作,如關(guān)閉數(shù)據(jù)庫(kù)等浙值。 - 實(shí)用方法
from_crawler(cls,crawler)
是一個(gè)類方法恳不,用@classmethod標(biāo)識(shí),是一種依賴注入的方式开呐⊙萄可以通過(guò)crawler對(duì)象拿到Scrapy的所有核心組件。
參數(shù):cls就是Class
返回:Class實(shí)例
9.Downloader Middleware的用法
下載中間件筐付,是處于Scrapy的Request和Response之間的處理模塊卵惦。
- 在Scheduler調(diào)度出隊(duì)列的Request發(fā)送給Downloader下載之前,也就是可以在Request執(zhí)行下載之前對(duì)其進(jìn)行修改瓦戚。
- 在下載后生成的Response發(fā)送給Spider之前沮尿,可以在生成Response被Spider解析之前對(duì)其進(jìn)行修改。
優(yōu)先級(jí)問(wèn)題
數(shù)字越小越靠近Scrapy引擎较解,數(shù)字越大越靠近Downloader畜疾,數(shù)字小的Downloader Middleware會(huì)被優(yōu)先調(diào)用。
返回類型不同哨坪,產(chǎn)生的效果也不同庸疾。
- None。Scrapy繼續(xù)處理該Request当编,按照設(shè)置的優(yōu)先級(jí)順序依次執(zhí)行其他Downloader Middleware的process_request()方法對(duì)Request進(jìn)行修改届慈,一直到Downloader把Request執(zhí)行后得到Response才結(jié)束。
- Response對(duì)象忿偷。不再繼續(xù)調(diào)用更低優(yōu)先級(jí)的Downloader Middleware的process_request()和process_exception()方法金顿,每個(gè)Downloader Middleware的process_response()方法轉(zhuǎn)而被依次調(diào)用。調(diào)用完畢之后鲤桥,直接將Response對(duì)象發(fā)送給Spider來(lái)處理揍拆。
- Request對(duì)象。停止執(zhí)行更低優(yōu)先級(jí)的Downloader Middleware的process_request()方法茶凳。將此Request重新放到調(diào)度隊(duì)列里嫂拴,相當(dāng)于一個(gè)全新的Request播揪,等待被調(diào)度。
- IgnoreRequest異常拋出筒狠。所有的Downloader Middleware的process_exception()方法會(huì)依次執(zhí)行猪狈。若沒(méi)有一個(gè)方法處理這個(gè)異常,則Request的errorback()方法就會(huì)回調(diào)辩恼。如果該異常還沒(méi)有被處理雇庙,那么忽略。
核心方法有三個(gè)灶伊,只需要實(shí)現(xiàn)至少一個(gè)方法疆前,就可以定義一個(gè)Downloader Middleware
-
process_request(request, spider)
Request被Scrapy引擎調(diào)度給Downloader之前,該方法會(huì)被調(diào)用聘萨。也就是在Request從隊(duì)列里調(diào)度出來(lái)到Downloader下載執(zhí)行之前竹椒,我們都可以用process_request()方法對(duì)Request進(jìn)行處理。
返回:None/Response/Request/拋出IgnoreRequest異常匈挖。 -
process_response(request, response, spider)
收到Response后碾牌,在Scrapy引擎將Response發(fā)送給Spider進(jìn)行解析之前,可以用該方法對(duì)Response進(jìn)行處理儡循。
返回:Request對(duì)象/Response對(duì)象/拋出IgnoreRequest異常舶吗。 -
process_exception(request, exception, spider)
當(dāng)Downloader或process_request()方法拋出異常時(shí)該方法被調(diào)用。
返回:None/Response對(duì)象/Request對(duì)象择膝。
修改請(qǐng)求時(shí)的User-Agent的兩種方式:
- 只需要在setting.py中添加一行USER_AGENT的定義即可誓琼。
USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36'
- 設(shè)置隨機(jī)的User-Agent,需要借助Downloader Middleware的process_request()方法肴捉。
在middlewares.py里面添加一個(gè)RandomUserAgentMiddleware的類腹侣,如下所示:
import random
class RandomUserAgentMiddleware():
def __init__(self):
self.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"
]
def process_request(self, request, spider):
request.headers['User-Agent'] = random.choice(self.user_agents)
要使之生效,我們需要去調(diào)用這個(gè)Downloader Middleware齿穗。在settings.py中將DOWNLOADER_MIDDLEWARES取消注釋并設(shè)置成如下內(nèi)容:
DOWNLOADER_MIDDLEWARES = { 'scrapydownloadertest.middlewares.RandomUserAgentMiddleware': 543,
}
注意:scrapydownloadertest是你項(xiàng)目的文件夾名稱傲隶,記得修改哦。
10. Spider Middleware的用法
scrapy提供了內(nèi)置的基礎(chǔ)Spider Middleware窃页。如果想要擴(kuò)展其功能跺株,只需要實(shí)現(xiàn)某幾個(gè)方法即可。
每個(gè)Spider Middleware都定義了以下一個(gè)或多個(gè)方法的類脖卖,只需要實(shí)現(xiàn)其中一個(gè)方法就可以定義一個(gè)Spider Middleware乒省,核心方法有如下4個(gè):
-
process_spider_input(response,spider)
當(dāng)Response被Spider Middleware處理時(shí),該方法被調(diào)用畦木。
參數(shù):response袖扛,即被處理的Response;spider十籍,即該Response對(duì)應(yīng)的Spider蛆封。
返回:None(繼續(xù)處理Response唇礁,調(diào)用其他的Spider Middleware,指導(dǎo)Spider處理該Response)或拋出一個(gè)異常(調(diào)用Request的errback()方法娶吞,errback的輸出重新輸入到中間件中垒迂,使用process_spider_output()方法來(lái)處理械姻,當(dāng)其拋出異常時(shí)則調(diào)用process_spider_exception()來(lái)處理) -
process_spider_output(response,result,spider)
當(dāng)Spider處理Response返回結(jié)果時(shí)妒蛇,process_spider_output()方法被調(diào)用。
返回:包含Request或Item對(duì)象的可迭代對(duì)象楷拳。 -
process_spider_exception(response,exception,spider)
當(dāng)Spider或Spider Middleware的process_spider_input()方法拋出異常時(shí)绣夺,該方法被調(diào)用。
返回:None(Scrapy繼續(xù)調(diào)用其他Spider Middleware中的process_spider_exception()方法處理該異常欢揖,直到所有Spider Middleware都被調(diào)用)陶耍;可迭代對(duì)象(其他Spider Middleware的process_spider_output()方法被調(diào)用) -
process_start_requests(start_requests,spider)
以Spider啟動(dòng)的Request為參數(shù)被調(diào)用,執(zhí)行過(guò)程類似于process_spider_output()她混,但沒(méi)有相關(guān)聯(lián)的Response烈钞,并且必須返回Request。
返回:必須返回一個(gè)包含Request對(duì)象的可迭代對(duì)象坤按。
可迭代對(duì)象:Python中經(jīng)常使用for來(lái)對(duì)某個(gè)對(duì)象進(jìn)行遍歷毯欣,此時(shí)被遍歷的這個(gè)對(duì)象就是可迭代對(duì)象,像常見(jiàn)的list,tuple都是臭脓。如果給一個(gè)準(zhǔn)確的定義的話酗钞,就是只要它定義了可以返回一個(gè)迭代器的__ iter __ 方法,或者定義了可以支持下標(biāo)索引的__ getitem __方法(這些雙下劃線方法會(huì)在其他章節(jié)中全面解釋)来累,那么它就是一個(gè)可迭代對(duì)象砚作。