Python爬蟲(chóng) | Scrapy詳解

一.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)主程序

image

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

image

爬取全站數(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()
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末椒舵,一起剝皮案震驚了整個(gè)濱河市蚂踊,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌笔宿,老刑警劉巖犁钟,帶你破解...
    沈念sama閱讀 218,607評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異泼橘,居然都是意外死亡涝动,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門炬灭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)醋粟,“玉大人,你說(shuō)我怎么就攤上這事∶自福” “怎么了厦凤?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,960評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)育苟。 經(jīng)常有香客問(wèn)我较鼓,道長(zhǎng),這世上最難降的妖魔是什么违柏? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,750評(píng)論 1 294
  • 正文 為了忘掉前任博烂,我火速辦了婚禮,結(jié)果婚禮上勇垛,老公的妹妹穿的比我還像新娘脖母。我一直安慰自己,他們只是感情好闲孤,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布谆级。 她就那樣靜靜地躺著,像睡著了一般讼积。 火紅的嫁衣襯著肌膚如雪肥照。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,604評(píng)論 1 305
  • 那天勤众,我揣著相機(jī)與錄音舆绎,去河邊找鬼。 笑死们颜,一個(gè)胖子當(dāng)著我的面吹牛吕朵,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播窥突,決...
    沈念sama閱讀 40,347評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼努溃,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了阻问?” 一聲冷哼從身側(cè)響起梧税,我...
    開(kāi)封第一講書(shū)人閱讀 39,253評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎称近,沒(méi)想到半個(gè)月后第队,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,702評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡刨秆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評(píng)論 3 336
  • 正文 我和宋清朗相戀三年凳谦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片坛善。...
    茶點(diǎn)故事閱讀 40,015評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡晾蜘,死狀恐怖邻眷,靈堂內(nèi)的尸體忽然破棺而出眠屎,到底是詐尸還是另有隱情剔交,我是刑警寧澤,帶...
    沈念sama閱讀 35,734評(píng)論 5 346
  • 正文 年R本政府宣布改衩,位于F島的核電站岖常,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏葫督。R本人自食惡果不足惜竭鞍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望橄镜。 院中可真熱鬧偎快,春花似錦、人聲如沸洽胶。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,934評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)姊氓。三九已至丐怯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間翔横,已是汗流浹背读跷。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,052評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留禾唁,地道東北人效览。 一個(gè)月前我還...
    沈念sama閱讀 48,216評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像荡短,于是被迫代替她去往敵國(guó)和親丐枉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評(píng)論 2 355

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

  • 目錄前言架構(gòu)安裝第一個(gè)爬蟲(chóng):爬取有道翻譯創(chuàng)建項(xiàng)目創(chuàng)建Item創(chuàng)建Spider解析運(yùn)行爬蟲(chóng)-爬取單詞釋義下載單詞語(yǔ)音...
    所謂向日葵族閱讀 3,048評(píng)論 0 3
  • 前言 爬蟲(chóng)就是請(qǐng)求網(wǎng)站并提取數(shù)據(jù)的自動(dòng)化程序,其中請(qǐng)求烫映,提取沼本,自動(dòng)化是爬蟲(chóng)的關(guān)鍵。Python作為一款出色的膠水語(yǔ)...
    王奧OX閱讀 3,387評(píng)論 1 8
  • 一锭沟、將爬取到的數(shù)據(jù)存儲(chǔ)到磁盤文件和數(shù)據(jù)庫(kù)中各一份 Pipeline Settings 上述代碼中抽兆,字典中的兩組鍵值...
    生信師姐閱讀 478評(píng)論 0 4
  • 久違的晴天,家長(zhǎng)會(huì)族淮。 家長(zhǎng)大會(huì)開(kāi)好到教室時(shí)辫红,離放學(xué)已經(jīng)沒(méi)多少時(shí)間了凭涂。班主任說(shuō)已經(jīng)安排了三個(gè)家長(zhǎng)分享經(jīng)驗(yàn)。 放學(xué)鈴聲...
    飄雪兒5閱讀 7,523評(píng)論 16 22
  • 創(chuàng)業(yè)是很多人的夢(mèng)想贴妻,多少人為了理想和不甘選擇了創(chuàng)業(yè)來(lái)實(shí)現(xiàn)自我價(jià)值切油,我就是其中一個(gè)。 創(chuàng)業(yè)后名惩,我由女人變成了超人澎胡,什...
    亦寶寶閱讀 1,812評(píng)論 4 1