Scrapy詳解 爬蟲框架入門看這一篇就夠了!

前言

學(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ò)爬蟲。

Scrapy架構(gòu)圖.png

架構(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"}
proportion有道釋義.jpg

創(chuàng)建項(xiàng)目

在需要?jiǎng)?chuàng)建的目錄下捂襟,

scrapy startproject youdaoeng

回車即可創(chuàng)建默認(rèn)的Scrapy項(xiàng)目架構(gòu)咬腕。

youdaoeng項(xiàng)目架構(gòu).jpg

創(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文件。


開(kāi)始爬取.jpg
Item輸出.jpg
生成的data.json.jpg

生成的數(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è)單詞的釋義。

項(xiàng)目源碼

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末敛苇,一起剝皮案震驚了整個(gè)濱河市煤痕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖摆碉,帶你破解...
    沈念sama閱讀 212,542評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異脓豪,居然都是意外死亡巷帝,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門扫夜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)楞泼,“玉大人,你說(shuō)我怎么就攤上這事笤闯《槔” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 158,021評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵颗味,是天一觀的道長(zhǎng)超陆。 經(jīng)常有香客問(wèn)我,道長(zhǎng)浦马,這世上最難降的妖魔是什么时呀? 我笑而不...
    開(kāi)封第一講書人閱讀 56,682評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮晶默,結(jié)果婚禮上谨娜,老公的妹妹穿的比我還像新娘。我一直安慰自己磺陡,他們只是感情好趴梢,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,792評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著币他,像睡著了一般坞靶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上圆丹,一...
    開(kāi)封第一講書人閱讀 49,985評(píng)論 1 291
  • 那天滩愁,我揣著相機(jī)與錄音,去河邊找鬼辫封。 笑死硝枉,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的倦微。 我是一名探鬼主播妻味,決...
    沈念sama閱讀 39,107評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼欣福!你這毒婦竟也來(lái)了责球?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 37,845評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎雏逾,沒(méi)想到半個(gè)月后嘉裤,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,299評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡栖博,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,612評(píng)論 2 327
  • 正文 我和宋清朗相戀三年屑宠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片仇让。...
    茶點(diǎn)故事閱讀 38,747評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡典奉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出丧叽,到底是詐尸還是另有隱情卫玖,我是刑警寧澤,帶...
    沈念sama閱讀 34,441評(píng)論 4 333
  • 正文 年R本政府宣布踊淳,位于F島的核電站假瞬,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏嚣崭。R本人自食惡果不足惜笨触,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,072評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望雹舀。 院中可真熱鬧芦劣,春花似錦、人聲如沸说榆。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,828評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)签财。三九已至串慰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間唱蒸,已是汗流浹背邦鲫。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,069評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留神汹,地道東北人庆捺。 一個(gè)月前我還...
    沈念sama閱讀 46,545評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像屁魏,于是被迫代替她去往敵國(guó)和親滔以。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,658評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容