一.Scrapy框架簡(jiǎn)介
何為框架惯疙,就相當(dāng)于一個(gè)封裝了很多功能的結(jié)構(gòu)體吼畏,它幫我們把主要的結(jié)構(gòu)給搭建好了让网,我們只需往骨架里添加內(nèi)容就行。scrapy框架是一個(gè)為了爬取網(wǎng)站數(shù)據(jù)辞友,提取數(shù)據(jù)的框架栅哀,我們熟知爬蟲(chóng)總共有四大部分,請(qǐng)求称龙、響應(yīng)留拾、解析、存儲(chǔ)鲫尊,scrapy框架都已經(jīng)搭建好了痴柔。scrapy是基于twisted框架開(kāi)發(fā)而來(lái),twisted是一個(gè)流行的事件驅(qū)動(dòng)的python網(wǎng)絡(luò)框架疫向,scrapy使用了一種非阻塞(又名異步)的代碼實(shí)現(xiàn)并發(fā)的咳蔚,Scrapy之所以能實(shí)現(xiàn)異步,得益于twisted框架鸿捧。twisted有事件隊(duì)列屹篓,哪一個(gè)事件有活動(dòng)疙渣,就會(huì)執(zhí)行匙奴!Scrapy它集成高性能異步下載,隊(duì)列妄荔,分布式泼菌,解析,持久化等啦租。
1.五大核心組件
引擎(Scrapy)
框架核心哗伯,用來(lái)處理整個(gè)系統(tǒng)的數(shù)據(jù)流的流動(dòng), 觸發(fā)事務(wù)(判斷是何種數(shù)據(jù)流,然后再調(diào)用相應(yīng)的方法)篷角。也就是負(fù)責(zé)Spider焊刹、ItemPipeline、Downloader恳蹲、Scheduler中間的通訊虐块,信號(hào)、數(shù)據(jù)傳遞等嘉蕾,所以被稱為框架的核心贺奠。
調(diào)度器(Scheduler)
用來(lái)接受引擎發(fā)過(guò)來(lái)的請(qǐng)求,并按照一定的方式進(jìn)行整理排列错忱,放到隊(duì)列中儡率,當(dāng)引擎需要時(shí)挂据,交還給引擎《眨可以想像成一個(gè)URL(抓取網(wǎng)頁(yè)的網(wǎng)址或者說(shuō)是鏈接)的優(yōu)先隊(duì)列, 由它來(lái)決定下一個(gè)要抓取的網(wǎng)址是什么, 同時(shí)去除重復(fù)的網(wǎng)址崎逃。
下載器(Downloader)
負(fù)責(zé)下載引擎發(fā)送的所有Requests請(qǐng)求,并將其獲取到的Responses交還給Scrapy Engine(引擎)眉孩,由引擎交給Spider來(lái)處理婚脱。Scrapy下載器是建立在twisted這個(gè)高效的異步模型上的。
爬蟲(chóng)(Spiders)
用戶根據(jù)自己的需求勺像,編寫程序障贸,用于從特定的網(wǎng)頁(yè)中提取自己需要的信息,即所謂的實(shí)體(Item)吟宦。用戶也可以從中提取出鏈接篮洁,讓Scrapy繼續(xù)抓取下一個(gè)頁(yè)面。跟進(jìn)的URL提交給引擎殃姓,再次進(jìn)入Scheduler(調(diào)度器)袁波。
項(xiàng)目管道(Pipeline)
負(fù)責(zé)處理爬蟲(chóng)提取出來(lái)的item,主要的功能是持久化實(shí)體蜗侈、驗(yàn)證實(shí)體的有效性篷牌、清除不需要的信息。
2.工作流程
Scrapy中的數(shù)據(jù)流由引擎控制踏幻,其過(guò)程如下:
(1)用戶編寫爬蟲(chóng)主程序?qū)⑿枰螺d的頁(yè)面請(qǐng)求requests遞交給引擎枷颊,引擎將請(qǐng)求轉(zhuǎn)發(fā)給調(diào)度器;
(2)調(diào)度實(shí)現(xiàn)了優(yōu)先級(jí)该面、去重等策略夭苗,調(diào)度從隊(duì)列中取出一個(gè)請(qǐng)求,交給引擎轉(zhuǎn)發(fā)給下載器(引擎和下載器中間有中間件隔缀,作用是對(duì)請(qǐng)求加工如:對(duì)requests添加代理题造、ua、cookie猾瘸,response進(jìn)行過(guò)濾等)界赔;
(3)下載器下載頁(yè)面,將生成的響應(yīng)通過(guò)下載器中間件發(fā)送到引擎牵触;
(4) 爬蟲(chóng)主程序進(jìn)行解析淮悼,這個(gè)時(shí)候解析函數(shù)將產(chǎn)生兩類數(shù)據(jù),一種是items荒吏、一種是鏈接(URL)敛惊,其中requests按上面步驟交給調(diào)度器;items交給數(shù)據(jù)管道(數(shù)據(jù)管道實(shí)現(xiàn)數(shù)據(jù)的最終處理)绰更;
官方文檔
英文版:https://docs.scrapy.org/en/latest/
http://doc.scrapy.org/en/master/
中文版:https://scrapy-chs.readthedocs.io/zh_CN/latest/intro/overview.html
https://www.osgeo.cn/scrapy/topics/architecture.html
二瞧挤、安裝及常用命令介紹
1. 安裝
Linux:pip3 install scrapy
Windows:
a. pip3 install wheel
b. 下載twisted http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
c. shift右擊進(jìn)入下載目錄锡宋,執(zhí)行 pip3 install typed_ast-1.4.0-cp36-cp36m-win32.whl
d. pip3 install pywin32
e. pip3 install scrapy
2.scrapy基本命令行
(1)創(chuàng)建一個(gè)新的項(xiàng)目
scrapy startproject ProjectName
(2)生成爬蟲(chóng)
scrapy genspider +SpiderName+website
(3)運(yùn)行(crawl) # -o output
scrapy crawl +SpiderName
scrapy crawl SpiderName -o file.json
scrapy crawl SpiderName-o file.csv
(4)檢查spider文件是否有語(yǔ)法錯(cuò)誤
scrapy check
(5)list返回項(xiàng)目所有spider名稱
scrapy list
(6)測(cè)試電腦當(dāng)前爬取速度性能:
scrapy bench
(7)scrapy runspider
scrapy runspider zufang_spider.py
(8)編輯spider文件:
scrapy edit <spider> 相當(dāng)于打開(kāi)vim模式,實(shí)際并不好用特恬,在IDE中編輯更為合適执俩。
(9)將網(wǎng)頁(yè)內(nèi)容下載下來(lái),然后在終端打印當(dāng)前返回的內(nèi)容癌刽,相當(dāng)于 request 和 urllib 方法:
scrapy fetch <url> (10)將網(wǎng)頁(yè)內(nèi)容保存下來(lái)役首,并在瀏覽器中打開(kāi)當(dāng)前網(wǎng)頁(yè)內(nèi)容,直觀呈現(xiàn)要爬取網(wǎng)頁(yè)的內(nèi)容:
scrapy view <url> (11)進(jìn)入終端显拜。打開(kāi) scrapy 顯示臺(tái)衡奥,類似ipython,可以用來(lái)做測(cè)試:
scrapy shell [url]
(12)輸出格式化內(nèi)容:
scrapy parse <url> [options]
(13)返回系統(tǒng)設(shè)置信息:
scrapy settings [options]
如:
$ scrapy settings --get BOT_NAME
scrapybot
(14)顯示scrapy版本:
scrapy version [-v]
后面加 -v 可以顯示scrapy依賴庫(kù)的版本
三远荠、簡(jiǎn)單實(shí)例
以麥田租房信息爬取為例矮固,網(wǎng)站http://bj.maitian.cn/zfall/PG1
1.創(chuàng)建項(xiàng)目
scrapy startproject houseinfo
生成項(xiàng)目結(jié)構(gòu):
scrapy.cfg 項(xiàng)目的主配置信息。(真正爬蟲(chóng)相關(guān)的配置信息在settings.py文件中)
items.py 設(shè)置數(shù)據(jù)存儲(chǔ)模板譬淳,用于結(jié)構(gòu)化數(shù)據(jù)档址,如:Django的Model
pipelines 數(shù)據(jù)持久化處理
settings.py 配置文件
spiders 爬蟲(chóng)目錄
2.創(chuàng)建爬蟲(chóng)應(yīng)用程序
cd houseinfo
scrapy genspider maitian maitian.com
然后就可以在spiders目錄下看到我們的爬蟲(chóng)主程序
3.編寫爬蟲(chóng)文件
步驟2執(zhí)行完畢后,會(huì)在項(xiàng)目的spiders中生成一個(gè)應(yīng)用名的py爬蟲(chóng)文件邻梆,文件源碼如下:
# -*- coding: utf-8 -*-
import scrapy
class MaitianSpider(scrapy.Spider):
name = 'maitian' # 應(yīng)用名稱
allowed_domains = ['maitian.com'] #一般注釋掉守伸,允許爬取的域名(如果遇到非該域名的url則爬取不到數(shù)據(jù))
start_urls = ['http://maitian.com/'] #起始爬取的url列表,該列表中存在的url浦妄,都會(huì)被parse進(jìn)行請(qǐng)求的發(fā)送
#解析函數(shù)
def parse(self, response):
pass
我們可以在此基礎(chǔ)上尼摹,根據(jù)需求進(jìn)行編寫
# -*- coding: utf-8 -*-
import scrapy
class MaitianSpider(scrapy.Spider):
name = 'maitian'
start_urls = ['http://bj.maitian.cn/zfall/PG100']
#解析函數(shù)
def parse(self, response):
li_list = response.xpath('//div[@class="list_wrap"]/ul/li')
results = []
for li in li_list:
title = li.xpath('./div[2]/h1/a/text()').extract_first().strip()
price = li.xpath('./div[2]/div/ol/strong/span/text()').extract_first().strip()
square = li.xpath('./div[2]/p[1]/span[1]/text()').extract_first().replace('㎡','') # 將面積的單位去掉
area = li.xpath('./div[2]/p[2]/span/text()[2]').extract_first().strip().split('\xa0')[0] # 以空格分隔
adress = li.xpath('./div[2]/p[2]/span/text()[2]').extract_first().strip().split('\xa0')[2]
dict = {
"標(biāo)題":title,
"月租金":price,
"面積":square,
"區(qū)域":area,
"地址":adress
}
results.append(dict)
print(title,price,square,area,adress)
return results
須知:
- xpath為scrapy中的解析方式
- xpath函數(shù)返回的為列表,列表中存放的數(shù)據(jù)為Selector類型數(shù)據(jù)校辩。解析到的內(nèi)容被封裝在Selector對(duì)象中窘问,需要調(diào)用extract()函數(shù)將解析的內(nèi)容從Selec****t****or中取出辆童。
- 如果可以保證xpath返回的列表中只有一個(gè)列表元素宜咒,則可以使用extract_first(),****否則必須使用extract()
兩者等同,都是將列表中的內(nèi)容提取出來(lái)
title = li.xpath('./div[2]/h1/a/text()').extract_first().strip()
title = li.xpath('./div[2]/h1/a/text()')[0].extract().strip()
4. 設(shè)置修改settings.py配置文件相關(guān)配置:
# 偽裝請(qǐng)求載體身份
USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36'
# 可以忽略或者不遵守robots協(xié)議
ROBOTSTXT_OBEY = False
5.執(zhí)行爬蟲(chóng)程序:scrapy crawl maitain
爬取全站數(shù)據(jù)把鉴,也就是全部頁(yè)碼數(shù)據(jù)故黑。本例中,總共100頁(yè)庭砍,觀察頁(yè)面之間的共性场晶,構(gòu)造通用url
方式一:通過(guò)占位符,構(gòu)造通用url
import scrapy
class MaitianSpider(scrapy.Spider):
name = 'maitian'
start_urls = ['http://bj.maitian.cn/zfall/PG{}'.format(page) for page in range(1,4)] #注意寫法
#解析函數(shù)
def parse(self, response):
li_list = response.xpath('//div[@class="list_wrap"]/ul/li')
results = []
for li in li_list:
title = li.xpath('./div[2]/h1/a/text()').extract_first().strip()
price = li.xpath('./div[2]/div/ol/strong/span/text()').extract_first().strip()
square = li.xpath('./div[2]/p[1]/span[1]/text()').extract_first().replace('㎡','')
# 也可以通過(guò)正則匹配提取出來(lái)
area = li.xpath('./div[2]/p[2]/span/text()[2]')..re(r'昌平|朝陽(yáng)|東城|大興|豐臺(tái)|海淀|石景山|順義|通州|西城')[0]
adress = li.xpath('./div[2]/p[2]/span/text()[2]').extract_first().strip().split('\xa0')[2]
dict = {
"標(biāo)題":title,
"月租金":price,
"面積":square,
"區(qū)域":area,
"地址":adress
}
results.append(dict)
return results
如果碰到一個(gè)表達(dá)式不能包含所有情況的項(xiàng)目怠缸,解決方式是先分別寫表達(dá)式诗轻,最后通過(guò)列表相加,將所有url合并成一個(gè)url列表揭北,例如
start_urls = ['http://www.guokr.com/ask/hottest/?page={}'.format(n) for n in range(1, 8)] + [
'http://www.guokr.com/ask/highlight/?page={}'.format(m) for m in range(1, 101)]
方式二:通過(guò)重寫start_requests方法扳炬,獲取所有的起始url吏颖。(不用寫start_urls
)
import scrapy
class MaitianSpider(scrapy.Spider):
name = 'maitian'
def start_requests(self):
pages=[]
for page in range(90,100):
url='http://bj.maitian.cn/zfall/PG{}'.format(page)
page=scrapy.Request(url)
pages.append(page)
return pages
#解析函數(shù)
def parse(self, response):
li_list = response.xpath('//div[@class="list_wrap"]/ul/li')
results = []
for li in li_list:
title = li.xpath('./div[2]/h1/a/text()').extract_first().strip(),
price = li.xpath('./div[2]/div/ol/strong/span/text()').extract_first().strip(),
square = li.xpath('./div[2]/p[1]/span[1]/text()').extract_first().replace('㎡',''),
area = li.xpath('./div[2]/p[2]/span/text()[2]').re(r'昌平|朝陽(yáng)|東城|大興|豐臺(tái)|海淀|石景山|順義|通州|西城')[0],
adress = li.xpath('./div[2]/p[2]/span/text()[2]').extract_first().strip().split('\xa0')[2]
dict = {
"標(biāo)題":title,
"月租金":price,
"面積":square,
"區(qū)域":area,
"地址":adress
}
results.append(dict)
return results
四、數(shù)據(jù)持久化存儲(chǔ)
- 基于終端指令的持久化存儲(chǔ)
- 基于管道的持久化存儲(chǔ)
只要是數(shù)據(jù)持久化存儲(chǔ)恨樟,parse方法必須有返回值(也就是return后的內(nèi)容)
1. 基于終端指令的持久化存儲(chǔ)
執(zhí)行輸出指定格式進(jìn)行存儲(chǔ):將爬取到的數(shù)據(jù)寫入不同格式的文件中進(jìn)行存儲(chǔ)半醉,windows終端不能使用txt格式
scrapy crawl 爬蟲(chóng)名稱 -o xxx.json
scrapy crawl 爬蟲(chóng)名稱 -o xxx.xml
scrapy crawl 爬蟲(chóng)名稱 -o xxx.csv
以麥田為例,spider中的代碼不變劝术,將返回值寫到qiubai.csv中缩多。本地沒(méi)有,就會(huì)自己創(chuàng)建一個(gè)养晋。本地有就會(huì)追加
scrapy crawl maitian -o maitian.csv
就會(huì)在項(xiàng)目目錄下看到衬吆,生成的文件
查看文件內(nèi)容
2.基于管道的持久化存儲(chǔ)
scrapy框架中已經(jīng)為我們專門集成好了高效、便捷的持久化操作功能绳泉,我們直接使用即可咆槽。要想使用scrapy的持久化操作功能,我們首先來(lái)認(rèn)識(shí)如下兩個(gè)文件:
- items.py:數(shù)據(jù)結(jié)構(gòu)模板文件圈纺。定義數(shù)據(jù)屬性秦忿。
- pipelines.py:管道文件。接收數(shù)據(jù)(items)蛾娶,進(jìn)行持久化操作灯谣。
持久化流程:
①爬蟲(chóng)文件爬取到數(shù)據(jù)解析后,需要將數(shù)據(jù)封裝到items對(duì)象中蛔琅。
②使用yield關(guān)鍵字將items對(duì)象提交給pipelines管道胎许,進(jìn)行持久化操作。
③在管道文件中的process_item方法中接收爬蟲(chóng)文件提交過(guò)來(lái)的item對(duì)象罗售,然后編寫持久化存儲(chǔ)的代碼辜窑,將item對(duì)象中存儲(chǔ)的數(shù)據(jù)進(jìn)行持久化存儲(chǔ)(在管道的process_item方法中執(zhí)行io操作,進(jìn)行持久化存儲(chǔ))
④settings.py配置文件中開(kāi)啟管道
2.1保存到本地的持久化存儲(chǔ)
爬蟲(chóng)文件:maitian.py
import scrapy
from houseinfo.items import HouseinfoItem # 將item導(dǎo)入
class MaitianSpider(scrapy.Spider):
name = 'maitian'
start_urls = ['http://bj.maitian.cn/zfall/PG100']
#解析函數(shù)
def parse(self, response):
li_list = response.xpath('//div[@class="list_wrap"]/ul/li')
for li in li_list:
item = HouseinfoItem(
title = li.xpath('./div[2]/h1/a/text()').extract_first().strip(),
price = li.xpath('./div[2]/div/ol/strong/span/text()').extract_first().strip(),
square = li.xpath('./div[2]/p[1]/span[1]/text()').extract_first().replace('㎡',''),
area = li.xpath('./div[2]/p[2]/span/text()[2]').extract_first().strip().split('\xa0')[0],
adress = li.xpath('./div[2]/p[2]/span/text()[2]').extract_first().strip().split('\xa0')[2]
)
yield item # 提交給管道,然后管道定義存儲(chǔ)方式
items文件:items.py
import scrapy
class HouseinfoItem(scrapy.Item):
title = scrapy.Field() #存儲(chǔ)標(biāo)題寨躁,里面可以存儲(chǔ)任意類型的數(shù)據(jù)
price = scrapy.Field()
square = scrapy.Field()
area = scrapy.Field()
adress = scrapy.Field()
管道文件:pipelines.py
class HouseinfoPipeline(object):
def __init__(self):
self.file = None
#開(kāi)始爬蟲(chóng)時(shí)穆碎,執(zhí)行一次
def open_spider(self,spider):
self.file = open('maitian.csv','a',encoding='utf-8') # 選用了追加模式
self.file.write(",".join(["標(biāo)題","月租金","面積","區(qū)域","地址","\n"]))
print("開(kāi)始爬蟲(chóng)")
# 因?yàn)樵摲椒〞?huì)被執(zhí)行調(diào)用多次,所以文件的開(kāi)啟和關(guān)閉操作寫在了另外兩個(gè)只會(huì)各自執(zhí)行一次的方法中职恳。
def process_item(self, item, spider):
content = [item["title"], item["price"], item["square"], item["area"], item["adress"], "\n"]
self.file.write(",".join(content))
return item
# 結(jié)束爬蟲(chóng)時(shí)所禀,執(zhí)行一次
def close_spider(self,spider):
self.file.close()
print("結(jié)束爬蟲(chóng)")
配置文件:settings.py
#偽裝請(qǐng)求載體身份
USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36'
#可以忽略或者不遵守robots協(xié)議
ROBOTSTXT_OBEY = False
#開(kāi)啟管道
ITEM_PIPELINES = {
'houseinfo.pipelines.HouseinfoPipeline': 300, #數(shù)值300表示為優(yōu)先級(jí),值越小優(yōu)先級(jí)越高
}
五放钦、爬取多級(jí)頁(yè)面
爬取多級(jí)頁(yè)面色徘,會(huì)遇到2個(gè)問(wèn)題:
問(wèn)題1:如何對(duì)下一層級(jí)頁(yè)面發(fā)送請(qǐng)求?
答:在每一個(gè)解析函數(shù)的末尾操禀,通過(guò)Request方法對(duì)下一層級(jí)的頁(yè)面手動(dòng)發(fā)起請(qǐng)求
# 先提取二級(jí)頁(yè)面url褂策,再對(duì)二級(jí)頁(yè)面發(fā)送請(qǐng)求。多級(jí)頁(yè)面以此類推
def parse(self, response):
next_url = response.xpath('//div[2]/h2/a/@href').extract()[0] # 提取二級(jí)頁(yè)面url
yield scrapy.Request(url=next_url, callback=self.next_parse) # 對(duì)二級(jí)頁(yè)面發(fā)送請(qǐng)求,注意要用yield斤寂,回調(diào)函數(shù)不帶括號(hào)
問(wèn)題2:解析的數(shù)據(jù)不在同一張頁(yè)面中蔑水,最終如何將數(shù)據(jù)傳遞
答:涉及到請(qǐng)求傳參,可以在對(duì)下一層級(jí)頁(yè)面發(fā)送請(qǐng)求的時(shí)候扬蕊,通過(guò)meta參數(shù)進(jìn)行數(shù)據(jù)傳遞搀别,meta字典就會(huì)傳遞給回調(diào)函數(shù)的response參數(shù)。下一級(jí)的解析函數(shù)通過(guò)response獲取item(先通過(guò) response.meta返回接收到的meta字典尾抑,再獲得item字典)
# 通過(guò)meta參數(shù)進(jìn)行Request的數(shù)據(jù)傳遞歇父,meta字典就會(huì)傳遞給回調(diào)函數(shù)的response參數(shù)
def parse(self, response):
item = Item() # 實(shí)例化item對(duì)象
Item["field1"] = response.xpath('expression1').extract()[0] # 列表中只有一個(gè)元素
Item["field2"] = response.xpath('expression2').extract() # 列表
next_url = response.xpath('expression3').extract()[0] # 提取二級(jí)頁(yè)面url
# meta參數(shù):請(qǐng)求傳參.通過(guò)meta參數(shù)進(jìn)行Request的數(shù)據(jù)傳遞,meta字典就會(huì)傳遞給回調(diào)函數(shù)的response參數(shù)
yield scrapy.Request(url=next_url, callback=self.next_parse,meta={'item':item}) # 對(duì)二級(jí)頁(yè)面發(fā)送請(qǐng)求
def next_parse(self,response):
# 通過(guò)response獲取item. 先通過(guò) response.meta返回接收到的meta字典,再獲得item字典
item = response.meta['item']
item['field'] = response.xpath('expression').extract_first()
yield item #提交給管道
案例1:麥田再愈,對(duì)所有頁(yè)碼發(fā)送請(qǐng)求榜苫。不推薦將每一個(gè)頁(yè)碼對(duì)應(yīng)的url存放到爬蟲(chóng)文件的起始url列表(start_urls)中。這里我們使用Request方法手動(dòng)發(fā)起請(qǐng)求翎冲。
# -*- coding: utf-8 -*-
import scrapy
from houseinfo.items import HouseinfoItem # 將item導(dǎo)入
class MaitianSpider(scrapy.Spider):
name = 'maitian'
start_urls = ['http://bj.maitian.cn/zfall/PG1']
#爬取多頁(yè)
page = 1
url = 'http://bj.maitian.cn/zfall/PG%d'
#解析函數(shù)
def parse(self, response):
li_list = response.xpath('//div[@class="list_wrap"]/ul/li')
for li in li_list:
item = HouseinfoItem(
title = li.xpath('./div[2]/h1/a/text()').extract_first().strip(),
price = li.xpath('./div[2]/div/ol/strong/span/text()').extract_first().strip(),
square = li.xpath('./div[2]/p[1]/span[1]/text()').extract_first().replace('㎡',''),
area = li.xpath('./div[2]/p[2]/span/text()[2]').re(r'昌平|朝陽(yáng)|東城|大興|豐臺(tái)|海淀|石景山|順義|通州|西城')[0], # 也可以通過(guò)正則匹配提取出來(lái)
adress = li.xpath('./div[2]/p[2]/span/text()[2]').extract_first().strip().split('\xa0')[2]
)
['http://bj.maitian.cn/zfall/PG{}'.format(page) for page in range(1, 4)]
yield item # 提交給管道垂睬,然后管道定義存儲(chǔ)方式
if self.page < 4:
self.page += 1
new_url = format(self.url%self.page) # 這里的%是拼接的意思
yield scrapy.Request(url=new_url,callback=self.parse) # 手動(dòng)發(fā)起一個(gè)請(qǐng)求,注意一定要寫yield
案例2:這個(gè)案例比較好的一點(diǎn)是抗悍,parse函數(shù)驹饺,既有對(duì)下一頁(yè)的回調(diào),又有對(duì)詳情頁(yè)的回調(diào)
import scrapy
class QuotesSpider(scrapy.Spider):
name = 'quotes_2_3'
start_urls = [
'http://quotes.toscrape.com',
]
allowed_domains = [
'toscrape.com',
]
def parse(self,response):
for quote in response.css('div.quote'):
yield{
'quote': quote.css('span.text::text').extract_first(),
'author': quote.css('small.author::text').extract_first(),
'tags': quote.css('div.tags a.tag::text').extract(),
}
author_page = response.css('small.author+a::attr(href)').extract_first()
authro_full_url = response.urljoin(author_page)
yield scrapy.Request(authro_full_url, callback=self.parse_author) # 對(duì)詳情頁(yè)發(fā)送請(qǐng)求缴渊,回調(diào)詳情頁(yè)的解析函數(shù)
next_page = response.css('li.next a::attr("href")').extract_first() # 通過(guò)css選擇器定位到下一頁(yè)
if next_page is not None:
next_full_url = response.urljoin(next_page)
yield scrapy.Request(next_full_url, callback=self.parse) # 對(duì)下一頁(yè)發(fā)送請(qǐng)求赏壹,回調(diào)自己的解析函數(shù)
def parse_author(self,response):
yield{
'author': response.css('.author-title::text').extract_first(),
'author_born_date': response.css('.author-born-date::text').extract_first(),
'author_born_location': response.css('.author-born-location::text').extract_first(),
'authro_description': response.css('.author-born-location::text').extract_first(),
案例3:爬取www.id97.com電影網(wǎng),將一級(jí)頁(yè)面中的電影名稱衔沼,類型蝌借,評(píng)分,二級(jí)頁(yè)面中的上映時(shí)間指蚁,導(dǎo)演菩佑,片長(zhǎng)進(jìn)行爬取。(多級(jí)頁(yè)面+傳參)
# -*- coding: utf-8 -*-
import scrapy
from moviePro.items import MovieproItem
class MovieSpider(scrapy.Spider):
name = 'movie'
allowed_domains = ['www.id97.com']
start_urls = ['http://www.id97.com/']
def parse(self, response):
div_list = response.xpath('//div[@class="col-xs-1-5 movie-item"]')
for div in div_list:
item = MovieproItem() item['name'] = div.xpath('.//h1/a/text()').extract_first()
item['score'] = div.xpath('.//h1/em/text()').extract_first()
item['kind'] = div.xpath('.//div[@class="otherinfo"]').xpath('string(.)').extract_first()
item['detail_url'] = div.xpath('./div/a/@href').extract_first()
#meta參數(shù):請(qǐng)求傳參.通過(guò)meta參數(shù)進(jìn)行Request的數(shù)據(jù)傳遞凝化,meta字典就會(huì)傳遞給回調(diào)函數(shù)的response參數(shù)
yield scrapy.Request(url=item['detail_url'],callback=self.parse_detail,meta={'item':item})
def parse_detail(self,response):
#通過(guò)response獲取item. 先通過(guò) response.meta返回接收到的meta字典,再獲得item字典
item = response.meta['item']
item['actor'] = response.xpath('//div[@class="row"]//table/tr[1]/a/text()').extract_first()
item['time'] = response.xpath('//div[@class="row"]//table/tr[7]/td[2]/text()').extract_first()
item['long'] = response.xpath('//div[@class="row"]//table/tr[8]/td[2]/text()').extract_first()
yield item #提交item到管道
以上案例都只貼出了爬蟲(chóng)主程序腳本稍坯,因篇幅原因,所以item缘圈、pipeline和settings等腳本未貼出劣光,可參考上面案例進(jìn)行編寫。
六糟把、Scrapy發(fā)送post請(qǐng)求
問(wèn)題:在之前代碼中,我們從來(lái)沒(méi)有手動(dòng)的對(duì)start_urls列表中存儲(chǔ)的起始url進(jìn)行過(guò)請(qǐng)求的發(fā)送牲剃,但是起始url的確是進(jìn)行了請(qǐng)求的發(fā)送遣疯,那這是如何實(shí)現(xiàn)的呢?
解答:其實(shí)是因?yàn)榕老x(chóng)文件中的爬蟲(chóng)類繼承到了Spider父類中的start_requests(self)這個(gè)方法凿傅,該方法就可以對(duì)start_urls列表中的url發(fā)起請(qǐng)求:
def start_requests(self):
for u in self.start_urls:
yield scrapy.Request(url=u,callback=self.parse)
注意:****該方法默認(rèn)的實(shí)現(xiàn)缠犀,是對(duì)起始的url發(fā)起get請(qǐng)求数苫,如果想發(fā)起post請(qǐng)求,則需要子類重寫該方法辨液。不過(guò)虐急,****一般情況下不用scrapy發(fā)post請(qǐng)求,用request模塊滔迈。
例:爬取百度翻譯
*- coding: utf-8 -*-
import scrapy
class PostSpider(scrapy.Spider):
name = 'post'
# allowed_domains = ['www.xxx.com']
start_urls = ['https://fanyi.baidu.com/sug']
def start_requests(self):
data = { # post請(qǐng)求參數(shù)
'kw':'dog'
}
for url in self.start_urls:
yield scrapy.FormRequest(url=url,formdata=data,callback=self.parse) # 發(fā)送post請(qǐng)求
def parse(self, response):
print(response.text)
七止吁、設(shè)置日志等級(jí)
在使用scrapy crawl spiderFileName運(yùn)行程序時(shí),在終端里打印輸出的就是scrapy的日志信息燎悍。
日志信息的種類:
ERROR : 一般錯(cuò)誤
WARNING : 警告
INFO : 一般的信息
DEBUG : 調(diào)試信息設(shè)置日志信息指定輸出:
在settings.py配置文件中敬惦,加入
LOG_LEVEL = ‘指定日志信息種類’即可。
LOG_FILE = 'log.txt'則表示將日志信息寫入到指定文件中進(jìn)行存儲(chǔ)谈山。
其他常用設(shè)置:
BOT_NAME
默認(rèn):“scrapybot”俄删,使用startproject命令創(chuàng)建項(xiàng)目時(shí),其被自動(dòng)賦值
CONCURRENT_ITEMS
默認(rèn)為100奏路,Item Process(即Item Pipeline)同時(shí)處理(每個(gè)response的)item時(shí)最大值
CONCURRENT_REQUEST
默認(rèn)為16畴椰,scrapy downloader并發(fā)請(qǐng)求(concurrent requests)的最大值
LOG_ENABLED
默認(rèn)為True,是否啟用logging
DEFAULT_REQUEST_HEADERS
默認(rèn)如下:{'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'en',}
scrapy http request使用的默認(rèn)header
LOG_ENCODING
默認(rèn)utt-8,logging中使用的編碼
LOG_LEVEL
默認(rèn)“DEBUG”鸽粉,log中最低級(jí)別迅矛,可選級(jí)別有:CRITICAL,ERROR,WARNING,DEBUG
USER_AGENT
默認(rèn):“Scrapy/VERSION(....)”,爬取的默認(rèn)User-Agent,除非被覆蓋
COOKIES_ENABLED=False潜叛,禁用cookies
八秽褒、同時(shí)運(yùn)行多個(gè)爬蟲(chóng)
實(shí)際開(kāi)發(fā)中,通常在同一個(gè)項(xiàng)目里會(huì)有多個(gè)爬蟲(chóng)威兜,多個(gè)爬蟲(chóng)的時(shí)候是怎么將他們運(yùn)行起來(lái)呢销斟?
運(yùn)行單個(gè)爬蟲(chóng)
import sys
from scrapy.cmdline import execute
if __name__ == '__main__':
execute(["scrapy","crawl","maitian","--nolog"])
然后運(yùn)行py文件即可運(yùn)行名為‘maitian‘的爬蟲(chóng)
同時(shí)運(yùn)行多個(gè)爬蟲(chóng)
步驟如下:
- 在spiders同級(jí)創(chuàng)建任意目錄,如:commands
- 在其中創(chuàng)建 crawlall.py 文件 (此處文件名就是自定義的命令)
- 在settings.py 中添加配置 COMMANDS_MODULE = '項(xiàng)目名稱.目錄名稱'
- 在項(xiàng)目目錄執(zhí)行命令:scrapy crawlall
crawlall.py代碼
from scrapy.commands import ScrapyCommand
from scrapy.utils.project import get_project_settings
class Command(ScrapyCommand):
requires_project = True
def syntax(self):
return '[options]'
def short_desc(self):
return 'Runs all of the spiders'
def run(self, args, opts):
spider_list = self.crawler_process.spiders.list()
for name in spider_list:
self.crawler_process.crawl(name, **opts.__dict__)
self.crawler_process.start()