- 目錄
前言
學(xué)習(xí)Scrapy有一段時(shí)間了终蒂,當(dāng)時(shí)想要獲取一下百度漢字的解析仆潮,又不想一個(gè)個(gè)漢字去搜,復(fù)制粘貼太費(fèi)勁肯污,考慮到爬蟲的便利性,這篇文章是介紹一個(gè)爬蟲框架--Scrapy吨枉,非常主流的爬蟲框架仇箱,寫爬蟲還不會(huì)Scrapy,你就out啦??~
??爬蟲的應(yīng)用場(chǎng)景:
- 搜索多個(gè)漢字东羹,存儲(chǔ)下來(lái)漢字的解析
- 每隔一段時(shí)間獲取一下最新天氣,新聞等等
- 拿到豆瓣電影(豆瓣圖書)的top100的電影名字忠烛、演員属提、上映時(shí)間以及各大網(wǎng)友的評(píng)論
- 需要下載網(wǎng)站的一系列圖片,視頻等美尸,下載慕課網(wǎng)的課程視頻
- 搜集安居客的所有房源冤议,性價(jià)比分析
- 刷票、搶票
- 拿到微博當(dāng)前的熱門話題师坎,自媒體需要即時(shí)寫文章啦
- ...
架構(gòu)
官方解析:Scrapy是一個(gè)為了爬取網(wǎng)站數(shù)據(jù)恕酸,提取結(jié)構(gòu)性數(shù)據(jù)而編寫的應(yīng)用框架】杪可以應(yīng)用在包括數(shù)據(jù)挖掘蕊温,信息處理或存儲(chǔ)歷史數(shù)據(jù)等一系列的程序中袱箱。
其最初是為了頁(yè)面抓取(更確切來(lái)說(shuō),網(wǎng)絡(luò)抓纫迕)所設(shè)計(jì)的发笔,也可以應(yīng)用在獲取API所返回的數(shù)據(jù)或者通用的網(wǎng)絡(luò)爬蟲。
架構(gòu)分析:
-
Scrapy Engine
:Scrapy引擎凉翻。負(fù)責(zé)控制數(shù)據(jù)流在系統(tǒng)中所有組件中流動(dòng)了讨,并在相應(yīng)動(dòng)作發(fā)生時(shí)觸發(fā)事件。 -
Scheduler
:調(diào)度器制轰。從Scrapy Engine接受請(qǐng)求(requests)并排序列入隊(duì)列前计,并在引擎再次請(qǐng)求時(shí)返回。用它來(lái)決定下一個(gè)抓取的網(wǎng)址是什么垃杖,同時(shí)去除重復(fù)的網(wǎng)址男杈。 -
Downloader
:下載器。抓取網(wǎng)頁(yè)并將網(wǎng)頁(yè)內(nèi)容返還給Spiders缩滨。建立在twisted異步模型势就。 -
Spiders
:爬蟲。用戶自定義的類脉漏,主要用來(lái)解析網(wǎng)頁(yè)苞冯,提取Items,發(fā)送url跟進(jìn)等新請(qǐng)求等侧巨。 -
Item Pipelines
:管道舅锄。主要用來(lái)處理Spider解析出來(lái)的Items,進(jìn)行按規(guī)則過(guò)濾司忱,驗(yàn)證皇忿,持久化存儲(chǔ)(如數(shù)據(jù)庫(kù)存儲(chǔ))等 -
Downloader Middlewares
:下載中間件。位于Scrapy Engine和Downloader之間坦仍,主要是處理Scrapy引擎與下載器之間的請(qǐng)求及響應(yīng)鳍烁。 -
Spider Middlewares
:爬蟲中間件。位于Scrapy Engine和Spiders之間繁扎,主要工作是處理Spiders的響應(yīng)輸入和請(qǐng)求輸出幔荒。 -
Scheduler Middlewares
:調(diào)度中間件。位于Scrapy Engine和Scheduler之間梳玫。主要工作是處理從Scrapy Engine發(fā)送到Scheduler的請(qǐng)求和響應(yīng)爹梁。
數(shù)據(jù)處理流程:
1、引擎打開(kāi)一個(gè)網(wǎng)站(open a domain)提澎,找到處理該網(wǎng)站的Spider并向該Spider請(qǐng)求要爬取的第一個(gè)start_urls姚垃。
2、引擎從Spider中獲取到第一個(gè)要爬取的URL并在調(diào)度器(Scheduler)以Request調(diào)度盼忌。
3积糯、引擎向調(diào)度器請(qǐng)求下一個(gè)要爬取的URL掂墓。
4、調(diào)度器返回下一個(gè)要爬取的URL給引擎絮宁,引擎將URL通過(guò)Downloader Middlewares
(request)轉(zhuǎn)發(fā)給下載器(Downloader)梆暮。
5、一旦頁(yè)面下載完畢绍昂,Downloader
生成一個(gè)該頁(yè)面的Response啦粹,并將其通過(guò)Downloader Middlewares
(response)發(fā)送給引擎。
6窘游、引擎從Downloader
中接收到Response并通過(guò)Spider Middlewares
(request)發(fā)送給Spider處理唠椭。
7、Spider處理Response并返回爬取到的Item及(跟進(jìn)的)新的Request給引擎忍饰。
8贪嫂、引擎將(Spider返回的)爬取到的Item給Item Pipeline
,將(Spider返回的)Request給調(diào)度器艾蓝。
9力崇、系統(tǒng)重復(fù)2-9的操作,直到調(diào)度中沒(méi)有更多地request赢织,然后斷開(kāi)引擎與網(wǎng)站之間的聯(lián)系亮靴。
安裝
依賴環(huán)境:
- Python 2.7及以上
- Python Package: pip and setuptools. 現(xiàn)在 pip 依賴 setuptools ,如果未安裝于置,則會(huì)自動(dòng)安裝 setuptools 茧吊。
使用pip安裝:
pip install Scrapy
創(chuàng)建項(xiàng)目:
scrapy startproject [項(xiàng)目名]
如創(chuàng)建 scrapy startproject qimairank,會(huì)自動(dòng)創(chuàng)建Scrapy的項(xiàng)目架構(gòu):
qimairank
|--qimairank
|--spiders
|--__init__.py
|--__init__.py
|--items.py
|--middlewares.py
|--pipelines.py
|--settings.py
|--scrapy.cfg
-
scrapy.cfg
:項(xiàng)目的配置文件八毯,指定settings
文件搓侄,部署deploy的project名稱等等。 -
qimairank
:項(xiàng)目的python模塊话速。 -
spiders
:放置spider代碼的目錄讶踪。 -
items.py
:項(xiàng)目中的item文件。 -
pipelines.py
:項(xiàng)目中的pipelines文件泊交。 -
middlewares.py
:項(xiàng)目的中間件俊柔。 -
settings.py
:Scrapy 配置文件。更多配置信息查看:https://scrapy-chs.readthedocs.io/zh_CN/0.24/topics/settings.html
第一個(gè)爬蟲:爬取有道翻譯
熟悉Scrapy框架后活合,我們手寫第一個(gè)爬蟲,爬取有道翻譯的單詞發(fā)音物赶,發(fā)音文件鏈接白指,釋義,例句酵紫。
如單詞proportion
:有道翻譯的詳情連接為 http://dict.youdao.com/w/eng/proportion 告嘲。本篇文章爬取的內(nèi)容結(jié)果:
{"example": [{"en": "I seemed to have lost all sense of proportion.",
"zh": "我好象已經(jīng)喪失了有關(guān)比例的一切感覺(jué)错维。"},
{"en": "The price of this article is out of(all) proportion to its value.",
"zh": "這個(gè)商品的價(jià)格與它的價(jià)值完全不成比例。"},
{"en": "But, the use of interception bases on the violation of the citizen rights, so it should be satisfactory of the principle of legal reservation and the principle of proportion.",
"zh": "但是橄唬,監(jiān)聽(tīng)的適用是以侵害公民權(quán)利為前提的赋焕,因此監(jiān)聽(tīng)在刑事偵查中的運(yùn)用必須滿足法律保留原則和比例原則的要求。"}],
"explain": ["n. 比例仰楚,占比隆判;部分;面積僧界;均衡", "vt. 使成比例侨嘀;使均衡;分?jǐn)?],
"pron": "[pr?'p???(?)n]",
"pron_url": "http://dict.youdao.com/dictvoice?audio=proportion&type=1",
"word": "proportion"}
創(chuàng)建項(xiàng)目
在需要?jiǎng)?chuàng)建的目錄下捂襟,
scrapy startproject youdaoeng
回車即可創(chuàng)建默認(rèn)的Scrapy項(xiàng)目架構(gòu)咬腕。
創(chuàng)建Item
創(chuàng)建YoudaoengItem繼承scrapy.Item,并定義需要存儲(chǔ)的單詞葬荷,發(fā)音涨共,發(fā)音文件鏈接,釋義宠漩,例句举反。
import scrapy
class YoudaoengItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
# 單詞
word = scrapy.Field()
# 英式發(fā)音
pron = scrapy.Field()
# 發(fā)音audio文件鏈接
pron_url = scrapy.Field()
# 釋義
explain = scrapy.Field()
# 例句
example = scrapy.Field()
創(chuàng)建Spider
在spiders
目錄下創(chuàng)建EngSpider.py
,并創(chuàng)建class EngSpider
哄孤,繼承于Spider照筑。
from scrapy import Spider
class EngSpider(Spider):
name = "EngSpider"
# 允許訪問(wèn)的域
allowed_domains = ["dict.youdao.com"]
start_urls = [
'http://dict.youdao.com/w/eng/agree', 'http://dict.youdao.com/w/eng/prophet',
'http://dict.youdao.com/w/eng/proportion']
def parse(self, response):
pass
-
name
:用于區(qū)別Spider,該名字必須是唯一的瘦陈。 -
start_urls
:Spider在啟動(dòng)時(shí)進(jìn)行爬取的url列表凝危,首先會(huì)爬取第一個(gè)。 -
def parse(self, response)
:得到請(qǐng)求url后的response信息的解析方法晨逝。
有道翻譯的網(wǎng)址為http://dict.youdao.com/ 蛾默,根據(jù)分析,查詢英文單詞結(jié)果后鏈接更改捉貌,如查詢agree
支鸡,跳轉(zhuǎn)單詞詳情地址為http://dict.youdao.com/w/eng/agree 。所以幾乎可以認(rèn)為單詞的詳情頁(yè)鏈接可以是http://dict.youdao.com/w/eng/ 拼接上單詞本身趁窃,所以配置start_urls
我們查詢?nèi)齻€(gè)單詞的釋義詳情牧挣。
解析
解析用的Selectors選擇器有多種方法:
- xpath(): 傳入xpath表達(dá)式,返回該表達(dá)式所對(duì)應(yīng)的所有節(jié)點(diǎn)的selector list列表 醒陆。
- css(): 傳入CSS表達(dá)式瀑构,返回該表達(dá)式所對(duì)應(yīng)的所有節(jié)點(diǎn)的selector list列表.
- extract(): 序列化該節(jié)點(diǎn)為unicode字符串并返回list。
- re(): 根據(jù)傳入的正則表達(dá)式對(duì)數(shù)據(jù)進(jìn)行提取刨摩,返回unicode字符串list列表寺晌。
下面我們用xpath()選擇節(jié)點(diǎn)世吨,xpath的語(yǔ)法可參考w3c的http://www.w3school.com.cn/xpath/xpath_nodes.asp 學(xué)習(xí),需要熟悉語(yǔ)法呻征、運(yùn)算符耘婚、函數(shù)等。
def parse(self, response):
box = response.xpath('//*[@id="results-contents"]')
word = YoudaoengItem()
# 簡(jiǎn)明釋義
box_simple = box.xpath('.//*[@id="phrsListTab"]')
# 判斷查出來(lái)的字是否存在
if box_simple:
# 單詞
word['word'] = box_simple.xpath('.//h2[@class="wordbook-js"]//span[@class="keyword"]/text()').extract()[0]
# 英式發(fā)音
word['pron'] = box_simple.xpath(
'.//h2[@class="wordbook-js"]//div[@class="baav"]//*[@class="phonetic"]/text()').extract()[0]
# 發(fā)音鏈接
word['pron_url'] = "http://dict.youdao.com/dictvoice?audio=" + word['word'] + "&type=1"
# 釋義
word['explain'] = []
temp = box_simple.xpath('.//div[@class="trans-container"]//ul//li/text()').extract()
for item in temp:
if len(item) > 0 and not re.search(r'\n', item) and not re.match(r' ', item):
print(item)
word['explain'].append(item)
# 例句
time.sleep(3)
word['example'] = []
example_root = box.xpath('//*[@id="bilingual"]//ul[@class="ol"]/li')
# 1.雙語(yǔ)例句是否存在
if example_root:
for li in example_root:
en = ""
for span in li.xpath('./p[1]/span'):
if span.xpath('./text()').extract():
en += span.xpath('./text()').extract()[0]
elif span.xpath('./b/text()').extract():
en += span.xpath('./b/text()').extract()[0]
zh = str().join(li.xpath('./p[2]/span/text()').extract()).replace(' ', '')
word['example'].append(dict(en=en.replace('\"', '\\"'), zh=zh))
# 2.柯林斯英漢雙解大辭典的例句是否存在
elif box.xpath('//*[@id="collinsResult"]//ul[@class="ol"]//div[@class="examples"]'):
example_root = box.xpath('//*[@id="collinsResult"]//ul[@class="ol"]//li')
for i in example_root:
if i.xpath('.//*[@class="exampleLists"]'):
en = i.xpath(
'.//*[@class="exampleLists"][1]//div[@class="examples"]/p[1]/text()').extract()[0]
zh = i.xpath(
'.//*[@class="exampleLists"][1]//div[@class="examples"]/p[2]/text()').extract()[0]
word['example'].append(dict(en=en.replace('\"', '\\"'), zh=zh))
if len(word['example']) >= 3:
break
yield word
最后 yield word
則是返回解析的word 給Item Pipeline
陆赋,進(jìn)行隨后的數(shù)據(jù)過(guò)濾或者存儲(chǔ)沐祷。
運(yùn)行爬蟲-爬取單詞釋義
運(yùn)行爬蟲,會(huì)爬取agree奏甫、prophet戈轿、proportion三個(gè)單詞的詳情,在項(xiàng)目目錄下(scrapy.cfg所在的目錄)
youdaoeng>scrapy crawl EngSpider -o data.json
即可運(yùn)行阵子,窗口可以看見(jiàn)爬取的日志內(nèi)容輸出思杯,運(yùn)行結(jié)束后會(huì)在項(xiàng)目目錄下生成一個(gè)data.json文件。
生成的數(shù)據(jù)為所有item的json格式數(shù)組挠进,中文字符都是Unicode編碼色乾,可通過(guò)一些在線的json解析網(wǎng)站如 https://www.bejson.com/ ,Unicode轉(zhuǎn)中文查看是我們想要的結(jié)果领突。
下載單詞語(yǔ)音文件
單詞讀音的mp3鏈接為解析時(shí)候保存的pron_url
字段暖璧,接下來(lái)我們下載單詞mp3文件到本地。
在Item下增加屬性pron_save_path
君旦,存儲(chǔ)發(fā)音文件的本地地址:
# 發(fā)音 mp3 本地存放路徑
pron_save_path = scrapy.Field()
并在settings.py文件中配置下載文件的目錄澎办,如在D:\scrapy_files\目錄下,則配置
FILES_STORE = "D:\\scrapy_files\\"
增加ItemPipeline重新發(fā)起文件下載請(qǐng)求:
class Mp3Pipeline(FilesPipeline):
'''
自定義文件下載管道
'''
def get_media_requests(self, item, info):
'''
根據(jù)文件的url發(fā)送請(qǐng)求(url跟進(jìn))
:param item:
:param info:
:return:
'''
# meta攜帶的數(shù)據(jù)可以在response獲取到
yield scrapy.Request(url=item['pron_url'], meta={'item': item})
def item_completed(self, results, item, info):
'''
處理請(qǐng)求結(jié)果
:param results:
:param item:
:param info:
:return:
'''
file_paths = [x['path'] for ok, x in results if ok]
if not file_paths:
raise DropItem("Item contains no files")
# old_name = FILES_STORE + file_paths[0]
# new_name = FILES_STORE + item['word'] + '.mp3'
# 文件重命名 (相當(dāng)于剪切)
# os.rename(old_name, new_name)
# item['pron_save_path'] = new_name
# 返回的result是除去FILES_STORE的目錄
item['pron_save_path'] = FILES_STORE + file_paths[0]
return item
def file_path(self, request, response=None, info=None):
'''
自定義文件保存路徑
默認(rèn)的保存路徑是在FILES_STORE下創(chuàng)建的一個(gè)full來(lái)存放金砍,如果我們想要直接在FILES_STORE下存放局蚀,則需要自定義存放路徑。
默認(rèn)下載的是無(wú)后綴的文件恕稠,需要增加.mp3后綴
:param request:
:param response:
:param info:
:return:
'''
file_name = request.meta['item']['word'] + ".mp3"
return file_name
需要更改settings.py文件琅绅,配置Mp3Pipeline,后面的300為優(yōu)先級(jí)鹅巍,數(shù)字越大千扶,優(yōu)先級(jí)越低。
# Configure item pipelines
# See https://doc.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
# 'youdaoeng.pipelines.YoudaoengPipeline': 300,
'youdaoeng.pipelines.Mp3Pipeline': 300,
}
運(yùn)行
youdaoeng>scrapy crawl EngSpider -o data1.json
等待運(yùn)行完成骆捧,則在項(xiàng)目目錄下生成了data1.json澎羞,并在D:\scrapy_files\目錄下生成了我們爬取的三個(gè)單詞的釋義。