python筆記26(爬蟲進階)

1、scrapy框架簡介

一、什麼是Scrapy?

  Scrapy是一個爲了爬取網站數據,提取結構性數據而編寫的應用框架,很是出名,很是強悍。所謂的框架就是一個已經被集成了各類功能(高性能異步下載,隊列,分佈式,解析,持久化等)的具備很強通用性的項目模板。對於框架的學習,重點是要學習其框架的特性、各個功能的用法便可。css

二、安裝

  Linux:html

      pip3 install scrapypython

 

  Windows:mysql

      a. pip3 install wheellinux

      b. 下載twisted http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted(下載被編譯過的數據)正則表達式

      c. 進入下載目錄,執行 pip3 install Twisted‑17.1.0‑cp35‑cp35m‑win_amd64.whlredis

      d. pip3 install pywin32sql

      e. pip3 install scrapy數據庫

備註:輸入scrapy檢查若是有對應的版本信息則表示安裝完成。json

三、Scrapy核心組件介紹

 

      • 引擎(Scrapy)
        用來處理整個系統的數據流處理, 觸發事務(框架核心)
      • 調度器(Scheduler)
        用來接受引擎發過來的請求, 壓入隊列中, 並在引擎再次請求的時候返回. 能夠想像成一個URL(抓取網頁的網址或者說是連接)的優先隊列, 由它來決定下一個要抓取的網址是什麼, 同時去除重複的網址
      • 下載器(Downloader)
        用於下載網頁內容, 並將網頁內容返回給蜘蛛(Scrapy下載器是創建在twisted這個高效的異步模型上的)
      • 爬蟲(Spiders)
        爬蟲是主要幹活的, 用於從特定的網頁中提取本身須要的信息, 即所謂的實體(Item)。用戶也能夠從中提取出連接,讓Scrapy繼續抓取下一個頁面
      • 項目管道(Pipeline)
        負責處理爬蟲從網頁中抽取的實體,主要的功能是持久化實體、驗證明體的有效性、清除不須要的信息。當頁面被爬蟲解析後,將被髮送到項目管道,並通過幾個特定的次序處理數據。

2、scrapy框架基礎使用

  1)建立項目:scrapy startproject 項目名稱

    項目結構:

project_name/
   scrapy.cfg:
   project_name/
       __init__.py
       items.py
       pipelines.py
       settings.py
       spiders/
           __init__.py


scrapy.cfg   項目的主配置信息。(真正爬蟲相關的配置信息在settings.py文件中)
items.py     設置數據存儲模板,用於結構化數據,如:Django的Model
pipelines    數據持久化處理
settings.py  配置文件,如:遞歸的層數、併發數,延遲下載等
spiders      爬蟲目錄,如:建立文件,編寫爬蟲解析規則

  2)建立爬蟲應用程序:

      cd project_name(進入項目目錄)

      scrapy genspider 應用名稱 爬取網頁的起始url (例如:scrapy genspider qiubai www.qiushibaike.com)

  3)編寫爬蟲文件:在步驟2執行完畢後,會在項目的spiders中生成一個應用名的py爬蟲文件,文件源碼以下:

# -*- coding: utf-8 -*-
import scrapy


class QiubaiSpider(scrapy.Spider):
    #爬蟲文件的名稱:能夠指定某一個具體的爬蟲文件
    name = 'qiubai' #應用名稱
    #容許爬取的域名(若是遇到非該域名的url則爬取不到數據)
    allowed_domains = ['https://www.qiushibaike.com/']
    #起始爬取的url:工程被執行後就能夠獲取該列表中的url所對應的頁面
    start_urls = ['https://www.qiushibaike.com/']

     #訪問起始URL並獲取結果後的回調函數,該函數的response參數就是向起始的url發送請求後,獲取的響應對象.
    #response參數:就是對起始url發起請求後的響應對象
    #該函數返回值必須爲可迭代對象或者NUll 
     def parse(self, response):
        print(response.text) #獲取字符串類型的響應內容
        print(response.body)#獲取字節類型的相應內容#

  4)設置修改settings.py配置文件相關配置

修改內容及其結果以下(假裝請求載體身份):
19行: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' 

22行:ROBOTSTXT_OBEY = False  #能夠忽略或者不遵照robots協議

  5)執行爬蟲程序:scrapy crawl  應用名稱

  不想打印日誌能夠執行:scrapy crawl  應用名稱  --nolog

3、scrapy框架持久化存儲

一、將糗百首頁中段子的內容和標題進行爬取,將解析的內容存在磁盤文件中

# -*- coding: utf-8 -*-
import scrapy


class QiubaiSpider(scrapy.Spider):
    name = 'qiubai'
    #allowed_domains = ['https://www.qiushibaike.com/']
    start_urls = ['https://www.qiushibaike.com/text/']

    def parse(self, response):
        #xpath爲response中的方法,能夠將xpath表達式直接做用於該函數中,xpath返回的列表元素爲Selector類型的對象
        odiv = response.xpath('//div[@id="content-left"]/div')
        content_list = [] #用於存儲解析到的數據
        for div in odiv:
            #xpath函數返回的爲列表,列表中存放的數據爲Selector類型的數據。咱們解析到的內容被封裝在了Selector對象中,須要調用extract()函數將解析的內容從Selecor中取出。
            # extract()能夠將Selector對象總存取的文本內容獲取,也可使用extract_first()
            # author = div.xpath('./div[1]/a[2]/h2/text()')[0].extract()
            author = div.xpath('./div[1]/a[2]/h2/text()').extract_first()
            content = div.xpath('.//div[@class="content"]/span//text()').extract()
            # 將列表轉化爲字符串
            content = "".join(content)
            #將解析到的內容封裝到字典中
            dic={
                '做者':author,
                '內容':content,
            }

            #將數據存儲到content_list這個列表中
            content_list.append(dic)

        return content_list

執行爬蟲程序:

執行輸出指定格式進行存儲:將爬取到的數據寫入不一樣格式的文件中進行存儲
    scrapy crawl qiubai -o qiubai.json
    scrapy crawl qiubai -o qiubai.xml
    scrapy crawl qiubai -o qiubai.csv

二、scrapy持久化操做:將爬取到糗百數據存儲寫入到文本文件中進行存儲

# -*- coding: utf-8 -*-
import scrapy

class QiubaiSpider(scrapy.Spider):
    name = 'qiubai'
    allowed_domains = ['https://www.qiushibaike.com/']
    start_urls = ['https://www.qiushibaike.com/text/']

    def parse(self, response):
        #xpath爲response中的方法,能夠將xpath表達式直接做用於該函數中
        odiv = response.xpath('//div[@id="content-left"]/div')
        with open('./data.txt', 'w') as fp:
            for div in odiv:
                 #xpath函數返回的爲列表,列表中存放的數據爲Selector類型的數據。咱們解析到的內容被封裝在了Selector對象中,須要調用extract()函數將解析的內容從Selecor中取出。
                author = div.xpath('./div[1]/a[2]/h2/text()').extract_first()
                content = div.xpath('.//div[@class="content"]/span//text()').extract()

                 #持久化存儲爬取到的內容
                 fp.write(author + ':' + content + '\n')

注意:上述代碼表示的持久化操做是咱們本身經過IO操做將數據進行的文件存儲。在scrapy框架中已經爲咱們專門集成好了高效、便捷的持久化操做功能,咱們直接使用便可。要想使用scrapy的持久化操做功能,咱們首先來認識以下兩個文件:

items.py:數據結構模板文件。定義數據屬性。
    pipelines.py:管道文件。接收數據(items),進行持久化操做。

持久化流程:
    1.爬蟲文件爬取到數據後,須要將數據封裝到items對象中。
    2.使用yield關鍵字將items對象提交給pipelines管道進行持久化操做。
    3.settings.py配置文件中開啓管道

小試牛刀:將糗事百科首頁中的段子和做者數據爬取下來,而後進行持久化存儲

爬蟲文件:qiubaiDemo.py

# -*- coding: utf-8 -*-
import scrapy
from secondblood.items import SecondbloodItem

class QiubaidemoSpider(scrapy.Spider):
    name = 'qiubaiDemo'
    allowed_domains = ['www.qiushibaike.com']
    start_urls = ['https://www.qiushibaike.com/text/']

    def parse(self, response):
        odiv = response.xpath('//div[@id="content-left"]/div')
        for div in odiv:
            # xpath函數返回的爲列表,列表中存放的數據爲Selector類型的數據。咱們解析到的內容被封裝在了Selector對象中,須要調用extract()函數將解析的內容從Selecor中取出。xpath返回的列表元素爲Selector類型的對象。
            #extract()能夠將Selector對象總存取的文本內容獲取,也可使用extract_first()
            #author = div.xpath('./div[1]/a[2]/h2/text()')[0].extract()
            author = div.xpath('./div[1]/a[2]/h2/text()').extract_first()
            content = div.xpath('.//div[@class="content"]/span//text()').extract()
            #將列表轉化爲字符串
            content = "".join(content)
            #實例化item對象
                #一、導入類PiplineproItem
                #二、實例化對象
            item = PiplineproItem()
                #三、將解析到的數據值存儲
            item['author'] = author
            item['content'] = content
                #四、將item對象提交給管道
            yield item

items文件:items.py

# -*- 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 PiplineproItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    # scrapy.Field()能夠存儲任意類型的數據
    author = scrapy.Field()  # 存儲做者
    content = scrapy.Field()  # 存儲段子內容

管道文件:pipelines.py

# -*- coding: utf-8 -*-

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html


class PiplineproPipeline(object):
    # 做用:每當爬蟲文向管道提交一次item,該方法就會被調用一次。
    # item參數就是接收到爬蟲文件給提交過來的item對象
    # 構造方法
    def __init__(self):
        self.fp = None  # 定義一個文件描述符屬性

    # 下列都是在重寫父類的方法:
    # 該方法只有在開始爬蟲的時候被調用一次
    def open_spider(self, spider):
        self.fp = open('./qiubai_data.txt', 'w', encoding='utf-8')
        print('開始爬蟲')

    # 由於該方法會被執行調用屢次,因此文件的開啓和關閉操做寫在了另外兩個只會各自執行一次的方法中。
    def process_item(self, item, spider):
        author = item['author']
        content = item['content']
        # 將爬蟲程序提交的item進行持久化存儲
        self.fp.write(author + ':' + content)
        return item

    # 該方法只有在開始結束的時候被調用一次
    def close_spider(self, spider):
        print('爬蟲結束')
        self.fp.close()

配置文件:settings.py

# 默認狀況下,scrapy框架沒有開啓管道功能,若是想用管道作數據持久化存儲,在此處給管道開啓ITEM_PIPELINES
#PiplineproPipeline在piplines.py中的類,300爲優先級,數值越小,優先級越高
# 在當前的項目中能夠寫多個管道文件,每個類都須要在此開啓
ITEM_PIPELINES = {
   'pipLinePro.pipelines.PiplineproPipeline': 300,
}

三、將糗百首頁中段子的內容和標題進行爬取,將數據存儲在mysql數據庫中。

1)建立表

#打開cmd,在終端輸入如下語句
mysql -uroot -p
create database scrapyDB;
user scrapyDB;
create table qiubai(author varchar(100),content varchar(9999));
sql語句

2)piplines.py

# -*- coding: utf-8 -*-

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html
import pymysql


class PiplineproPipeline(object):
    # 做用:每當爬蟲文向管道提交一次item,該方法就會被調用一次。
    # item參數就是接收到爬蟲文件給提交過來的item對象
    fp = None  # 定義一個文件描述符屬性

    # 下列都是在重寫父類的方法:
    # 該方法只有在開始爬蟲的時候被調用一次
    def open_spider(self, spider):
        self.fp = open('./qiubai_data.txt', 'w', encoding='utf-8')
        print('開始爬蟲')

    # 由於該方法會被執行調用屢次,因此文件的開啓和關閉操做寫在了另外兩個只會各自執行一次的方法中。
    def process_item(self, item, spider):
        author = item['author']
        content = item['content']
        # 將爬蟲程序提交的item進行持久化存儲
        self.fp.write(author + ':' + content)
        return item

    # 該方法只有在開始結束的時候被調用一次
    def close_spider(self, spider):
        print('爬蟲結束')
        self.fp.close()


class MyPipline(object):
    conn = None
    cursor = None

    def open_spider(self, spider):
        self.conn = pymysql.Connect(host="192.168.12.65", port=3306, db="scrapyDB", charset="utf8", user="root")
        self.cursor = self.conn.cursor()
        print('mysql鏈接成功')

    def process_item(self, item, spider):
        author = item['author']
        content = item['content']
        sql = "insert into qiubai values('%s','%s')" % (author, content)
        try:
            self.cursor.execute(sql)
            self.conn.commit()
        except Exception as e:
            self.conn.rollback()
        return item
pipelines.py

3)settings

ITEM_PIPELINES = {
    'pipLinePro.pipelines.PiplineproPipeline': 300,
    'pipLinePro.pipelines.MyPipline': 300,
}
settings

四、Scrapy遞歸爬取多頁數據

需求:將糗事百科全部頁碼的做者和段子內容數據進行爬取切持久化存儲

# -*- coding: utf-8 -*-
import scrapy
from choutiAllPro.items import ChoutiallproItem


class ChoutiSpider(scrapy.Spider):
    name = 'chouti'
    # allowed_domains = ['www.chouti.com']
    start_urls = ['https://dig.chouti.com/r/pic/hot/1']
    # 設計了一個全部頁面通用的url(pageNum表示不一樣的頁碼)
    pageNum = 1  # 起始頁碼
    url = 'https://dig.chouti.com/r/pic/hot/%d'  # 每頁的url

    def parse(self, response):
        div_list = response.xpath('//div[@class="content-list"]/div')
        for div in div_list:
            title = div.xpath('./div[3]/div[1]/a/text()').extract_first()
            item = ChoutiallproItem()
            item['title'] = title
            yield item  # 提交item到管道進行持久化存儲

        # 進行其餘頁碼對應url的請求操做,爬取全部頁碼數據
        if self.pageNum <= 10:
            self.pageNum += 1
            url = format(self.url % self.pageNum)
            print(url)
            # 遞歸爬取數據:callback參數的值爲回調函數(將url請求後,獲得的相應數據進行parse解析),遞歸調用parse函數
            yield scrapy.Request(url=url, callback=self.parse)
chouti.py

4、scrapy框架cookie和代理

一、cookie

在request模塊中可使用request.session獲取cookie,可是在scrapy中沒有request方法,起始在scrapy框架中,不須要額外的處理cookie,在scrapy框架中會額外的處理cookie,自動將cookie存儲,前幾個案例都是發起的get請求,下面案例使用scrqpy框架發post請求:
默認狀況下發起的爲get請求:

# -*- coding: utf-8 -*-
import scrapy


class PostSpider(scrapy.Spider):
    name = 'post'
    allowed_domains = ['www.xxx.com']
    start_urls = ['http://www.xxx.com/']

    # Spider中的start_requests方法,能夠將start_urls列表中的url依次默認進行get請求
    def start_requests(self):
        for url in self.start_urls:
            yield scrapy.Request(url=url, callback=self.parse)

    def parse(self, response):
        pass
start_requests方法

重寫Spider中的start_requests方法,將start_urls列表中的url依次進行post請求:

# -*- coding: utf-8 -*-
import scrapy

#需求:對start_urls列表中的url發起post請求
class PostSpider(scrapy.Spider):
    name = 'post'
    #allowed_domains = ['www.xxx.com']
    start_urls = ['https://fanyi.baidu.com/sug']

    # 重寫Spider中的start_requests方法,將start_urls列表中的url依次進行post請求
    def start_requests(self):
        for url in self.start_urls:
            #FormRequest():該方法能夠發起一個post請求
            yield scrapy.FormRequest(url=url, callback=self.parse,formdata={'kw':'dog'})

    def parse(self, response):
        print(response.text)
重寫start_requests

處理基於cookie的登陸操做:

# -*- coding: utf-8 -*-
import scrapy


class LoginSpider(scrapy.Spider):
    name = 'login'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['https://accounts.douban.com/login']

    def start_requests(self):
        data = {
            'source': 'movie',
            'redir': 'https://movie.douban.com/',
            'form_email': 'xxx',
            'form_password': 'xxx',
            'login': '登陸'
        }
        for url in self.start_urls:
            yield scrapy.FormRequest(url=url, callback=self.parse, formdata=data)

    # 單獨寫一個獲取頁面數據的方法
    def getPageText(self, response):
        page_text = response.text
        with open('./douban.html', 'w', encoding='utf-8') as fp:
            fp.write(page_text)
            print('over')

    def parse(self, response):
        # 對當前用戶的我的主頁頁面進行獲取
        url = 'https://www.douban.com/people/xxx/'
        yield scrapy.Request(url=url, callback=self.getPageText)
login.py

 二、代理

DOWNLOADER_MIDDLEWARES = {
   'dailiPro.middlewares.DailiproDownloaderMiddleware': 543,
}
settings.py
# -*- coding: utf-8 -*-

# Define here the models for your spider middleware
#
# See documentation in:
# https://doc.scrapy.org/en/latest/topics/spider-middleware.html

from scrapy import signals

# 爬蟲中間件
# class DailiproSpiderMiddleware(object):
#     # Not all methods need to be defined. If a method is not defined,
#     # scrapy acts as if the spider middleware does not modify the
#     # passed objects.
#
#     @classmethod
#     def from_crawler(cls, crawler):
#         # This method is used by Scrapy to create your spiders.
#         s = cls()
#         crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
#         return s
#
#     def process_spider_input(self, response, spider):
#         # Called for each response that goes through the spider
#         # middleware and into the spider.
#
#         # Should return None or raise an exception.
#         return None
#
#     def process_spider_output(self, response, result, spider):
#         # Called with the results returned from the Spider, after
#         # it has processed the response.
#
#         # Must return an iterable of Request, dict or Item objects.
#         for i in result:
#             yield i
#
#     def process_spider_exception(self, response, exception, spider):
#         # Called when a spider or process_spider_input() method
#         # (from other spider middleware) raises an exception.
#
#         # Should return either None or an iterable of Response, dict
#         # or Item objects.
#         pass
#
#     def process_start_requests(self, start_requests, spider):
#         # Called with the start requests of the spider, and works
#         # similarly to the process_spider_output() method, except
#         # that it doesn’t have a response associated.
#
#         # Must return only requests (not items).
#         for r in start_requests:
#             yield r
#
#     def spider_opened(self, spider):
#         spider.logger.info('Spider opened: %s' % spider.name)

# 下載中間件
class DailiproDownloaderMiddleware(object):
    # Not all methods need to be defined. If a method is not defined,
    # scrapy acts as if the downloader middleware does not modify the
    # passed objects.

    @classmethod
    def from_crawler(cls, crawler):
        # This method is used by Scrapy to create your spiders.
        s = cls()
        crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
        return s

    # 處理請求的方法
    def process_request(self, request, spider):
        # request參數表示的就是攔截到請求對象
        # request.meta = {'https':'151.106.15.3:1080'}
        request.meta['proxy'] = "https://151.106.15.3:1080"
        return None

    def process_response(self, request, response, spider):
        # Called with the response returned from the downloader.

        # Must either;
        # - return a Response object
        # - return a Request object
        # - or raise IgnoreRequest
        return response

    def process_exception(self, request, exception, spider):
        # Called when a download handler or a process_request()
        # (from other downloader middleware) raises an exception.

        # Must either:
        # - return None: continue processing this exception
        # - return a Response object: stops process_exception() chain
        # - return a Request object: stops process_exception() chain
        pass

    def spider_opened(self, spider):
        spider.logger.info('Spider opened: %s' % spider.name)
middlewares.py
# -*- coding: utf-8 -*-
import scrapy


class DailiSpider(scrapy.Spider):
    name = 'daili'
    allowed_domains = ['www.daili.com']
    start_urls = ['http://www.baidu.com/s?wd=ip']

    def parse(self, response):
        page_text = response.text
        with open('daili.html', 'w', encoding='utf-8') as fp:
            fp.write(page_text)
daili.py

 5、scrapy框架之日誌等級和請求傳參

一、Scrapy的日誌等級

- 在使用scrapy crawl spiderFileName運行程序時,在終端裏打印輸出的就是scrapy的日誌信息。

  - 日誌信息的種類:

        ERROR : 通常錯誤

        WARNING : 警告

        INFO : 通常的信息

        DEBUG : 調試信息

        默認的顯示級別是DEBUG

  - 設置日誌信息指定輸出:

    在settings.py配置文件中,加入LOG_LEVEL = ‘指定日誌信息種類’便可。LOG_FILE = 'log.txt'則表示將日誌信息寫入到指定文件中進行存儲。

二、請求傳參

- 在某些狀況下,咱們爬取的數據不在同一個頁面中,例如,咱們爬取一個電影網站,電影的名稱,評分在一級頁面,而要爬取的其餘電影詳情在其二級子頁面中。這時咱們就須要用到請求傳參。

  - 案例展現:爬取https://www.dy2018.com/html/gndy/dyzz/電影網,將一級頁面中的電影名稱,二級頁面中的導演爬取。

  爬蟲文件:

# -*- coding: utf-8 -*-
import scrapy
from moviePro.items import MovieproItem


class MovieSpider(scrapy.Spider):
    name = 'movie'
    # allowed_domains = ['www.movie.com']
    start_urls = ['https://www.dy2018.com/html/gndy/dyzz/']

    # 該方法能夠將電影詳情頁中的數據進行解析
    def getSencodPageText(self, reponse):
        item = reponse.meta['item']
        actor = reponse.xpath('//*[@id="Zoom"]/p[16]/text()').extract_first()
        item['actor'] = actor
        yield item

    def parse(self, response):
        table_list = response.xpath('//div[@class="co_content8"]/ul/table')
        for table in table_list:
            url = 'https://www.dy2018.com'+table.xpath('./tbody/tr[2]/td[2]/b/a/@href').extract_first()
            name = table.xpath('./tbody/tr[2]/td[2]/b/a/text()').extract_first()
            item = MovieproItem()
            item['name'] = name
            yield scrapy.Request(url=url, callback=self.getSencodPageText, meta={'item': item})
movie.py
# -*- 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 MovieproItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    name = scrapy.Field()
    actor = scrapy.Field()
items.py
# -*- coding: utf-8 -*-

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html


class MovieproPipeline(object):
    def process_item(self, item, spider):
        print(item['name']+":"+item['actor'])
        return item
pipelines.py

 6、scrapy框架之CrawlSpider操做

提問:若是想要經過爬蟲程序去爬取」糗百「全站數據新聞數據的話,有幾種實現方法?

方法一:基於Scrapy框架中的Spider的遞歸爬取進行實現(Request模塊遞歸回調parse方法)。

方法二:基於CrawlSpider的自動爬取進行實現(更加簡潔和高效)。

一、簡介

  CrawlSpider實際上是Spider的一個子類,除了繼承到Spider的特性和功能外,還派生除了其本身獨有的更增強大的特性和功能。其中最顯著的功能就是」LinkExtractors連接提取器「。Spider是全部爬蟲的基類,其設計原則只是爲了爬取start_url列表中網頁,而從爬取到的網頁中提取出的url進行繼續的爬取工做使用CrawlSpider更合適。

二、使用

  1.建立scrapy工程:scrapy startproject projectName

  2.建立爬蟲文件:scrapy genspider -t crawl spiderName www.xxx.com

    --此指令對比之前的指令多了 "-t crawl",表示建立的爬蟲文件是基於CrawlSpider這個類的,而再也不是Spider這個基類。

  3.觀察生成的爬蟲文件

# -*- coding: utf-8 -*-
import scrapy
#導入CrawlSpider相關模塊
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule

#表示該爬蟲程序是基於CrawlSpider類的
class ChoutidemoSpider(CrawlSpider):

    name = 'choutiDemo'
    #allowed_domains = ['www.chouti.com']
    start_urls = ['http://www.chouti.com/']
    #表示爲提取Link規則
    rules = (
        Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True),
    )
    #解析方法
    def parse_item(self, response):
        i = {}
        #i['domain_id'] = response.xpath('//input[@id="sid"]/@value').extract()
        #i['name'] = response.xpath('//div[@id="name"]').extract()
        #i['description'] = response.xpath('//div[@id="description"]').extract()
        return i

   CrawlSpider類和Spider類的最大不一樣是CrawlSpider多了一個rules屬性,其做用是定義」提取動做「。在rules中能夠包含一個或多個Rule對象,在Rule對象中包含了LinkExtractor對象。

3.1 LinkExtractor:顧名思義,連接提取器。
    LinkExtractor(
         allow=r'Items/'# 知足括號中「正則表達式」的值會被提取,若是爲空,則所有匹配。
         deny=xxx,  # 知足正則表達式的則不會被提取。
         restrict_xpaths=xxx, # 知足xpath表達式的值會被提取
         restrict_css=xxx, # 知足css表達式的值會被提取
         deny_domains=xxx, # 不會被提取的連接的domains。 
    )
    - 做用:提取response中符合規則的連接。
    
3.2 Rule : 規則解析器。根據連接提取器中提取到的連接,根據指定規則提取解析器連接網頁中的內容。
     Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True)
    - 參數介紹:
      參數1:指定連接提取器
      參數2:指定規則解析器解析數據的規則(回調函數)
      參數3:是否將連接提取器繼續做用到連接提取器提取出的連接網頁中。當callback爲None,參數3的默認值爲true。
  
3.3 rules=( ):指定不一樣規則解析器。一個Rule對象表示一種提取規則。
  
3.4 CrawlSpider總體爬取流程:
    a)爬蟲文件首先根據起始url,獲取該url的網頁內容
    b)連接提取器會根據指定提取規則將步驟a中網頁內容中的連接進行提取
    c)規則解析器會根據指定解析規則將連接提取器中提取到的連接中的網頁內容根據指定的規則進行解析
    d)將解析數據封裝到item中,而後提交給管道進行持久化存儲

三、簡單代碼實戰應用

爬蟲文件:

# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from qiubaiBycrawl.items import QiubaibycrawlItem
import re

class QiubaitestSpider(CrawlSpider):
    name = 'qiubaiTest'
    #起始url
    start_urls = ['http://www.qiushibaike.com/']

    # 鏈接提取器(提取頁碼鏈接):從起始的url表示的頁面的源碼中進行指定鏈接的提取
    # allow參數:正則表達式,能夠將起始url頁面源碼數據中符合該規則的鏈接進行提取
    page_link = LinkExtractor(allow=r'/8hr/page/\d+/')

    rules = (
        # 規則解析器:將鏈接提取器提取到的鏈接對應的頁面數據進行指定(callback)規則的解析
        # follow=True:將鏈接提起器繼續做用到鏈接提取器提取出的頁面當中
        Rule(page_link, callback='parse_item', follow=True),
    )

    #自定義規則解析器的解析規則函數
    def parse_item(self, response):
        div_list = response.xpath('//div[@id="content-left"]/div')
     
        for div in div_list:
            #定義item
            item = QiubaibycrawlItem()
            #根據xpath表達式提取糗百中段子的做者
            item['author'] = div.xpath('./div/a[2]/h2/text()').extract_first().strip('\n')
            #根據xpath表達式提取糗百中段子的內容
            item['content'] = div.xpath('.//div[@class="content"]/span/text()').extract_first().strip('\n')

            yield item #將item提交至管道

item文件:

# -*- 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 QiubaibycrawlItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    author = scrapy.Field() #做者
    content = scrapy.Field() #內容

管道文件:

# -*- coding: utf-8 -*-
# Define your item pipelines here
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html

class QiubaibycrawlPipeline(object):
    def __init__(self):
        self.fp = None

    def open_spider(self,spider):
        print('開始爬蟲')
        self.fp = open('./data.txt','w')
       
    def process_item(self, item, spider):
        #將爬蟲文件提交的item寫入文件進行持久化存儲
        self.fp.write(item['author']+':'+item['content']+'\n')
        return item   

    def close_spider(self,spider):
        print('結束爬蟲')
        self.fp.close()

 7、scrapy框架之分佈式操做

一、redis簡單回顧

  1.啓動redis:

    mac/linux:   redis-server redis.conf

    windows: redis-server.exe redis-windows.conf

  2.對redis配置文件進行配置:

    - 註釋該行:bind 127.0.0.1,表示可讓其餘ip訪問redis

    - 將yes該爲no:protected-mode no,表示可讓其餘ip操做redis

二、scrapy基於redis的數據持久化操做流程

  1.安裝scrapy-redis組件:

    - pip install scrapy-redis

    - scrapy-redis是基於scrapy框架開發出的一套組件,其做用就是可讓scrapy實現分佈式爬蟲。

  2.編寫爬蟲文件:

    - 同以前scrapy中基於Spider或者CrawlSpider的編寫方式一致。

  3.編寫管道文件:

    - 在scrapy-redis組件中已經幫助咱們封裝好了一個專門用於鏈接存儲redis數據庫的管道(RedisPipeline),所以咱們直接使用便可,無需本身編寫管道文件。

  4.編寫配置文件:

    - 在settings.py中開啓管道,且指定使用scrapy-redis中封裝好的管道。

ITEM_PIPELINES = {
    'scrapy_redis.pipelines.RedisPipeline': 400
}

    - 該管道默認會鏈接且將數據存儲到本機的redis服務中,若是想要鏈接存儲到其餘redis服務中須要在settings.py中進行以下配置:

REDIS_HOST = 'redis服務的ip地址'
REDIS_PORT = 6379
REDIS_ENCODING = ‘utf-8’
REDIS_PARAMS = {‘password’:’123456’}

三、redis分佈式部署

  1.scrapy框架是否能夠本身實現分佈式?

    - 不能夠。緣由有二。

      其一:由於多臺機器上部署的scrapy會各自擁有各自的調度器,這樣就使得多臺機器沒法分配start_urls列表中的url。(多臺機器沒法共享同一個調度器)

      其二:多臺機器爬取到的數據沒法經過同一個管道對數據進行統一的數據持久出存儲。(多臺機器沒法共享同一個管道)

  2.redis實現分佈式基本流程:

    - 使用基於scrapy-redis組件中的爬蟲文件。

import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from movieproject.items import MovieprojectItem
#導入scrapy-redis中的模塊
from scrapy_redis.spiders import RedisCrawlSpider

class NnSpider(RedisCrawlSpider):
    name = 'nn' 
    allowed_domains = ['www.id97.com']
    #redis_key表示調度器中的隊列(將要爬取的頁面數據對應的url都須要放置到調度器隊列中)
    redis_key = 'nnspider:start_urls'

    # 根據規則提取全部的頁碼連接
    page_link = LinkExtractor(allow=r'/movie/\?page=\d')
    detail_link = LinkExtractor(restrict_xpaths='//div[contains(@class,"col-xs-1-5")]/div/a')
    # detail_link = LinkExtractor(allow=r'/movie/\d+\.html$')
    # follow : 是否跟進
    rules = (
        # 全部的頁碼不用處理,跟進便可
        Rule(page_link, follow=True),
        # 全部的詳情頁處理,不用跟進
        Rule(detail_link, callback='parse_item', follow=False),
    )

    def parse_item(self, response):
        # 建立一個item對象
        item = MovieprojectItem()
        # 電影海報
        item['post'] = response.xpath('//a[@class="movie-post"]/img/@src').extract_first()
        # 電影名字
        item['name'] = response.xpath('//h1').xpath('string(.)').extract_first()
        yield item

- 使用scrapy-redis組件中封裝好的調度器,將全部的url存儲到該指定的調度器中,從而實現了多臺機器的調度器共享。

# 使用scrapy-redis組件的去重隊列
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 使用scrapy-redis組件本身的調度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 是否容許暫停
SCHEDULER_PERSIST = True

- 使用scrapy-redis組件中封裝好的管道,將每臺機器爬取到的數據存儲經過該管道存儲到redis數據庫中,從而實現了多臺機器的管道共享。

ITEM_PIPELINES = {
   'scrapy_redis.pipelines.RedisPipeline': 400,
}

- 執行:scrapy runspider xxx.py,而後向調度器隊列中傳入起始url:lpush nnspider:start_urls "http://www.xxx.com/"

總結:

# 一、導入RedisCrawlSpider類,將爬蟲類的父類修改爲RedisCrawlSpider
# 二、將start_urls修改成redis_key屬性
# 三、編寫具體的解析代碼
# 四、將item提交到scrapy-redis組件被封裝好的管道里
# 五、將爬蟲文件中產生的url對應的請求對象所有都提交到scrapy-redis封裝好的調度器中
# 六、在配置文件中指明將爬取到的數據值存儲到哪個redis數據庫中
# 七、在redis.windows.conf文件中註釋"bind 127.0.0.1",不註釋表明當前的redis數據庫只容許本機訪問,修改保護模式"protected-mode no",yes表示只容許讀操做
# 八、啓動redis服務器:redis-server ./redis.windows.conf,啓動redis客戶端:redis-cli
# 九、進入爬蟲文件所在目錄,執行爬蟲文件:scrapy runspider 爬蟲文件名稱
# 十、向調度器中扔一個起始的url,打開redis客戶端,輸入指令lpush 調度器隊列名稱 起始url [value ...]

問答題:

1)接觸過幾種爬蟲模塊?(request  urllib)
2)robots協議是什麼?(門戶網站中經常使用的反爬手段,防君子不防小人)
3)如何處理驗證碼?(使用雲打碼平臺或人工識別)

4)掌握幾種數據解析的方式?(xpath、正則、bs4)

5)如何爬取動態加載的頁面數據?(selenuim+phantonjs或selenuim+谷歌無頭瀏覽器)

6)接觸過哪些反爬機制?如何處理?(robots協議  UA 封IP 驗證碼  數據加密  動態數據爬取  token(動態變化的參數能夠從頁面源碼中獲取後賦值給參數))

7)scrapy核心組件工做流程(五個核心組件:爬蟲文件、引擎、調度器、管道、下載器)

8)接觸過幾種爬蟲的類(scrapy 、crawl-scrapy、redis-scrapy)

9)如何實現分佈式流程

10)爬取到的數據如何進行數據分析(沒有講)

相關文章
相關標籤/搜索