Scrapy框架的應用

一, Scrapy

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

  • 高性能的網絡請求
  • 高性能的數據解析
  • 高性能的持久化存儲
  • 深度爬取
  • 全棧爬取
  • 分佈式
  • 中間件
  • 請求傳參
  • ...等等

環境的安裝:html

mac/linux:pip install scrapy
window:
- pip install wheel
- 下載twisted http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
- 進入下載目錄,執行 pip install Twisted‑17.1.0‑cp37‑cp37m‑win_amd64.whl
- pip install pywin32
- pip install scrapy

二, 基礎的使用

新建一個工程:python

  • scrapy startproject ProName
    • 目錄結構:
      • spiders(包):空包
      • settings:配置文件
        • 不聽從robots: ROBOTSTXT_OBEY = False
        • UA假裝: USER_AGENT = 'xxx'
        • 日誌等級的指定: LOG_LEVEL = 'ERROR'
  • cd ProName:進入到工程目錄中
  • 在spiders(爬蟲文件夾)中建立一個爬蟲文件
  • scrapy genspider spiderName www.xxx.com
  • 編寫代碼:主要的代碼會編寫在爬蟲文件中
  • 執行工程:scrapy crawl spiderName
# -*- coding: utf-8 -*-
import scrapy

# 爬蟲類: 父類 => Spider
class FirstSpider(scrapy.Spider):
    # 爬蟲文件的名稱: 當前爬蟲源文件的惟一標識
    name = 'first'
    # 容許的域名, 限定爬取的域名
    # allowed_domains = ['www.baidu.com']
    # 起始的url列表, 列表中存放的url均可以被scrapy進行異步的網絡請求
    start_urls = ['https://www.baidu.com/', 'https://www.taobao.com/']

    # 用做與數據解析
    # 參數: response就是響應對象
    def parse(self, response):
        print(response)

scrapy的數據解析與持久化存儲:mysql

需求: 爬取 https://dig.chouti.com/ 的文章標題與做者linux

extract()與extract_first(): 從xpath查詢結果對象(Selector)中取得數據web

scrapy的持久化存儲正則表達式

  • 基於終端指令進行持久化存儲redis

    • 只能夠將parse方法的返回值存儲到本地的磁盤文件(指定形式後綴)中sql

    • 支持: json, jsonlines, jl, csv, xml, marshall, picklechrome

    • scrapy crawl spiderName -o filePath

      # 用做與數據解析
      # 參數: response就是響應對象
      def parse(self, response):
          div_list = response.xpath('/html/body/main/div/div/div[1]/div/div[2]/div[1]/div')
          all_data = []
          for div in div_list:
              # 解析標題和做者信息
              # xpath在取標籤中存儲的文本數據時必需要使用extract(),extract_first()進行字符串的單獨提取
              title = div.xpath('.//div[@class="link-detail"]/a/text()')[0].extract()
              author = div.xpath('.//span[@class="left author-name"]/text()').extract_first()
              all_data.append({'title': title, 'author': author})
              # 基於終端指令的持久化存儲
              # scrapy crawl spiderName -o filePath
          return all_data
  • 基於管道進行持久化存儲(經常使用)

    編碼流程:

    1. 在爬蟲文件中進行數據解析
    2. 在item類中定義相關的屬性
    3. 將解析到的數據存儲到一個item類型的對象中
    4. 將item類型的對象提交給管道
    5. 管道類的process_item方法負責接收item,接收到後能夠對item實現任意形式的持久化存儲操做
    6. 在配置文件中開啓管道
    7. 一個管道類對應一種平臺的持久化存儲
# settings.py
# 開啓管道,註冊管道
ITEM_PIPELINES = {
    # 300表示優先級,數值越小優先級越高
   'firstblood.pipelines.FirstbloodPipeline': 300,
}
# items.py
import scrapy

class FirstbloodItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    # Field類型視爲一個萬能的數據類型
    title = scrapy.Field()
    author = scrapy.Field()
# pipelines.py
import pymysql

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

    # 重寫父類方法,該方法只會在爬蟲開始時被執行一次
    def open_spider(self, spider):
        self.conn = pymysql.Connection(host='127.0.0.1', port=3306, user='root', password='123', db='scrapy',
                                       charset='utf8')
    # process_item方法調用後就能夠接收爬蟲類提交來的item對象
    def process_item(self, item, spider):
        title = item['title']
        author = item['author']
        sql = 'insert into chouti values (%s, %s)'
        self.cursor = self.conn.cursor()
        # 事務處理
        try:
            self.cursor.execute(sql, [author, title])
            self.conn.commit()
        except Exception as e:
            print(e)
            # 回滾事務
            self.conn.rollback()
        return item # 將item傳遞給下個管道類

    def close_spider(self, spider):
        self.cursor.close()
        self.conn.close()
# first.py
# -*- coding: utf-8 -*-
import scrapy
from firstblood.items import FirstbloodItem

class FirstSpider(scrapy.Spider):
    name = 'first'
    start_urls = ['https://dig.chouti.com/']

    # 用做與數據解析
    # 參數: response就是響應對象
    def parse(self, response):
        div_list = response.xpath('/html/body/main/div/div/div[1]/div/div[2]/div[1]/div')
        for div in div_list:
            title = div.xpath('.//div[@class="link-detail"]/a/text()')[0].extract()
            author = div.xpath('.//span[@class="left author-name"]/text()').extract_first()
            # 實例化一個item類型的對象
            item = FirstbloodItem()
            # 給item對象的屬性賦值
            item['title'] = title
            item['author'] = author
            yield item  # 將item提交給管道

三, 圖片數據爬取(流數據的爬取)

案例:

爬取http://www.521609.com/daxuexiaohua/的圖片

scrapy中封裝了一個管道類(ImagesPipeline),基於該管道類,能夠實現圖片資源的請求和永久化存儲

編碼流程:

1.在爬蟲文件中解析出圖片的地址

2.將圖片地址封裝到item中且提交給管道

import scrapy
from getImg.items import GetimgItem

class ImgSpider(scrapy.Spider):
    name = 'Img'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['http://www.521609.com/daxuemeinv/']
    # 定義一個通用的url模板
    url = 'http://www.521609.com/daxuemeinv/list8%d.html'
    pageNum = 1

    def parse(self, response):
        li_list = response.xpath('//*[@id="content"]/div[2]/div[2]/ul/li')
        for li in li_list:
            img_src = 'http://www.521609.com' + li.xpath('./a[1]/img/@src').extract_first()
            item = GetimgItem()
            item['img_src'] = img_src
            yield item
            
        if self.pageNum < 5:
            self.pageNum += 1
            new_url = self.url % self.pageNum
            # 手動發請求,實現深度爬取
            yield scrapy.Request(new_url, callback=self.parse)

3.管道文件中自定義一個管道類(繼承ImagesPipeline)
4.重寫三個方法:
def get_media_requests(self, item, info):
def file_path(self, request, response=None, info=None):
def item_completed(self, results, item, info):

import scrapy
from scrapy.pipelines.images import ImagesPipeline
class GetimgPipeline(ImagesPipeline):

    # 該方法是用做與請求發送的
    def get_media_requests(self, item, info):
        # 對item中的img_src進行請求發送
        # 手動進行請求發送
        yield scrapy.Request(url=item['img_src'])

    # 用於指定文件路徑(文件夾 + 文件名稱)
    def file_path(self, request, response=None, info=None):
        # 存到settings中指定的文件夾中
        return request.url.split('/')[-1]

    # 將當前的item提交給下個管道類
    def item_completed(self, results, item, info):
        return item

5.在配置文件中開啓管道且加上IMAGES_STORE配置

# 自定義文件夾路徑
IMAGES_STORE = './imgLibs'

四, POST請求

在scrapy中如何進行手動請求發送?

yield scrapy.Request(url, callback)

在scrapy中如何進行post請求的發送?

# formdata是post提交的數據
yield scrapy.FormRequest(url, callback, formdata={})

如何對起始的url進行post請求的發送?

# 重寫父類的start_requests(self)方法
def start_requests(self):
    for url in self.start_urls:
        yield scrapy.FormRequest(url, callback=self.parse, formdata={})

五, 提高爬取數據的效率

增長併發:
    默認scrapy開啓的併發線程爲32個,能夠適當進行增長。在settings配置文件中修改CONCURRENT_REQUESTS = 100值爲100,併發設置成了爲100。

下降日誌級別:
    在運行scrapy時,會有大量日誌信息的輸出,爲了減小CPU的使用率。能夠設置log輸出信息爲INFO或者ERROR便可。在配置文件中編寫:LOG_LEVEL = ‘ERROR’

禁止cookie:
    若是不是真的須要cookie,則在scrapy爬取數據時能夠禁止cookie從而減小CPU的使用率,提高爬取效率。在配置文件中編寫:COOKIES_ENABLED = False

禁止重試:
    對失敗的HTTP進行從新請求(重試)會減慢爬取速度,所以能夠禁止重試。在配置文件中編寫:RETRY_ENABLED = False

減小下載超時:
    若是對一個很是慢的連接進行爬取,減小下載超時能夠能讓卡住的連接快速被放棄,從而提高效率。在配置文件中進行編寫:DOWNLOAD_TIMEOUT = 1 超時時間爲10s

六, 深度爬取

什麼叫深度爬取?

  • 爬取的數據沒有存在同一張頁面中

如何實現請求傳參?

  • Request(url,callback,meta={}):能夠將meta字典傳遞給callback
  • callback接收item:response.meta

需求:

爬取https://www.4567tv.tv/index.php/vod/show/class/%E5%8A%A8%E4%BD%9C/id/1.html的每一頁的電影名稱和簡介.

import scrapy
from moviePro.items import MovieproItem


class MovieSpider(scrapy.Spider):
    name = 'movie'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['https://www.4567tv.tv/index.php/vod/show/class/%E5%8A%A8%E4%BD%9C/id/1.html']
    # 通用url模板
    url = 'https://www.4567tv.tv/index.php/vod/show/class/動做/id/1/page/%d.html'
    pageNum = 2

    # 解析電影名稱和詳情頁的url
    def parse(self, response):
        li_list = response.xpath('/html/body/div[1]/div/div/div/div[2]/ul/li')
        for li in li_list:
            title = li.xpath('./div/div/h4/a/text()').extract_first()
            detail_url = 'https://www.4567tv.tv' + li.xpath('./div/div/h4/a/@href').extract_first()

            item = MovieproItem()
            item['title'] = title
            # 手動請求發送, 將meta字典傳遞給指定的callback(請求傳參)
            yield scrapy.Request(detail_url, callback=self.parse_detailxxx, meta={'item': item})

        if self.pageNum < 5:
            new_url = format(self.url % self.pageNum)
            self.pageNum += 1
            yield scrapy.Request(new_url, callback=self.parse)

    # 解析詳情頁中的電影簡介
    def parse_detailxxx(self, response):
        item = response.meta['item']
        desc = response.xpath('/html/body/div[1]/div/div/div/div[2]/p[5]/span[2]/text()').extract_first()
        item['desc'] = desc
        yield item

七, 五大核心組件

Scrapy Engine(引擎): 負責Spider、ItemPipeline、Downloader、Scheduler中間的通信,信號、數據傳遞等。

Scheduler(調度器): 它負責接受引擎發送過來的Request請求,並按照必定的方式進行整理排列,入隊,當引擎須要時,交還給引擎。

Downloader(下載器):負責下載Scrapy Engine(引擎)發送的全部Requests請求,並將其獲取到的Responses交還給Scrapy Engine(引擎),由引擎交給Spider來處理,

Spider(爬蟲):它負責處理全部Responses,從中分析提取數據,獲取Item字段須要的數據,並將須要跟進的URL提交給引擎,再次進入Scheduler(調度器).

Item Pipeline(管道):它負責處理Spider中獲取到的Item,並進行進行後期處理(詳細分析、過濾、存儲等)的地方。

Downloader Middlewares(下載中間件):你能夠看成是一個能夠自定義擴展下載功能的組件。 (經常使用,能夠取到request和response)

Spider Middlewares(Spider中間件):你能夠理解爲是一個能夠自定擴展和操做引擎和Spider中間通訊的功能組件(好比進入Spider的Responses;和從Spider出去的Requests)

執行流程:

1 引擎:Hi!Spider, 你要處理哪個網站?
2 Spider:老大要我處理xxxx.com。
3 引擎:你把第一個須要處理的URL給我吧。
4 Spider:給你,第一個URL是xxxxxxx.com。
5 引擎:Hi!調度器,我這有request請求你幫我排序入隊一下。
6 調度器:好的,正在處理你等一下。
7 引擎:Hi!調度器,把你處理好的request請求給我。
8 調度器:給你,這是我處理好的request
9 引擎:Hi!下載器,你按照老大的下載中間件的設置幫我下載一下這個request請求
10 下載器:好的!給你,這是下載好的東西。(若是失敗:sorry,這個request下載失敗了。而後引擎告訴調度器,這個request下載失敗了,你記錄一下,咱們待會兒再下載)
11 引擎:Hi!Spider,這是下載好的東西,而且已經按照老大的下載中間件處理過了,你本身處理一下(注意!這兒responses默認是交給def parse()這個函數處理的)
12 Spider:(處理完畢數據以後對於須要跟進的URL),Hi!引擎,我這裏有兩個結果,這個是我須要跟進的URL,還有這個是我獲取到的Item數據。
13 引擎:Hi !管道 我這兒有個item你幫我處理一下!調度器!這是須要跟進URL你幫我處理下。而後從第四步開始循環,直到獲取完老大須要所有信息。
14 管道調度器:好的,如今就作!

注意!只有當調度器中不存在任何request了,整個程序纔會中止,(也就是說,對於下載失敗的URL,Scrapy也會從新下載。)

八, 中間件

scrapy有哪些中間件?

  • 下載中間件(推薦)
  • 爬蟲中間件

下載中間件的做用:

  • 批量攔截全部的請求(request)和響應(response)

爲何攔截請求?

  • 篡改請求的頭信息(UA): request.headers['User-Agent'] = 'xxxxx'
  • 設置代理: request.meta['proxy'] = 'http://ip:port'

爲何攔截響應?

  • 篡改響應數據
  • 篡改響應對象(推薦)

案例分析:

爬取網易新聞(國內,國際,軍事,航空,無人機)新聞數據的標題和內容,而且調用百度AI對文章內容進行標籤提取和文章分類

分析:

1.每個板塊下對應的新聞數據都是動態加載出來的

2.會對五個板塊的響應數據進行數據解析,可是板塊對應的響應對象是不包含動態加載的新聞數據,目前獲取的每個板塊對應的響應對象是不知足需求的響應對象

3.將不知足需求的5個響應對象(工程中一共會有1+5+n),修改爲知足需求的響應對象。

# 爬蟲文件中
import scrapy
from NewsPro.items import NewsproItem
from selenium import webdriver
import time
from aip import AipNlp

class NewsSpider(scrapy.Spider):
    name = 'news'
    # allowed_domains = ['https://news.163.com']
    start_urls = ['https://news.163.com/']
    # 5個板塊的url
    model_urls = []
    # 打開selenium
    browser = webdriver.Chrome(executable_path='./chromedriver.exe')
    time.sleep(1)

    # 解析出每一個板塊對應的url
    def parse(self, response):
        li_index = [3, 4, 6, 7, 8]
        li_list = response.xpath('//div[@class="ns_area list"]/ul/li')
        for index in li_index:
            # 解析到每一個板塊的url
            model_url = li_list[index].xpath('./a/@href').extract_first()
            self.model_urls.append(model_url)
            # 對板塊的url進行請求發送獲取每個板塊對應的頁面源碼數據
            yield scrapy.Request(model_url, self.parse_model)

    # 請求每一個板塊的頁面源碼數據,解析出新聞的標題和詳情頁url
    def parse_model(self, response):
        div_list = response.xpath('//div[@class="ndi_main"]/div')
        for div in div_list:
            item = NewsproItem()
            new_url = div.xpath('./div/div[1]/h3/a/@href').extract_first()
            title = div.xpath('./div/div[1]/h3/a/text()').extract_first()
            if new_url and title:
                item['title'] = title
                # 對新聞的詳情頁進行請求發送,獲取詳情頁的頁面源碼數據
                yield scrapy.Request(new_url, self.parse_new, meta={'item': item})

    # 解析新聞詳情頁數據,並提交給管道
    def parse_new(self, response):
        content = response.xpath('//*[@id="endText"]//text()').extract()
        content = ''.join(content)
        item = response.meta['item']
        item['content'] = content
        yield item

    # 關閉selenium瀏覽器
    def closed(self, spider):
        self.browser.quit()
from scrapy import signals
import random
from time import sleep
from scrapy.http import HtmlResponse

user_agent_list = ['xxx', 'xxx']
class NewsproDownloaderMiddleware(object):

    def process_request(self, request, spider):
        # 經過中間件進行UA假裝,儘可能每次使用不一樣的UA
        request.headers['User-Agent'] = random.choice(user_agent_list)
        return None

    def process_response(self, request, response, spider):
        # selenium對象
        browser = spider.browser
        url = request.url
        # 判斷是否5個模塊的url
        if url in spider.model_urls:
            browser.get(url)
            sleep(1)
            # 下來滾軸,獲取所有的動態加載數據
            browser.execute_script('window.scrollTo(0,document.body.scrollHeight)')
            sleep(1)
            browser.execute_script('window.scrollTo(0,document.body.scrollHeight)')
            sleep(1)
            browser.execute_script('window.scrollTo(0,document.body.scrollHeight)')
            sleep(1)
            page_text = browser.page_source
            # 封裝一個response對象,替代不符合要求的response對象
            response = HtmlResponse(url=request.url, body=page_text, encoding='utf-8', request=request)
        return response

九, Crawl Spider

用途: 便捷的實現全站數據爬取

  1. 新建工程scrapy startproject proName
  2. cd proName
  3. 建立爬蟲文件scrapy genspider -t crawl spiderName www.xxx.com

核心:

連接提取器(LinkExtractor)

  • 能夠根據指定的規則對指定的連接進行提取
  • 提取的規則就是構造方法中的allow參數決定的,是一個正則表達式

規則解析器(Rule)

  • 能夠將連接提取器提取到的連接進行請求發送
  • 能夠根據指定的規則(callback)對請求到的數據進行解析
  • follow=True: 將連接提取器繼續做用到連接提取到的連接所對應的頁面源碼中,實現深度爬取

案例:

爬取http://wz.sun0769.com/html/top/report.shtml中全部頁文章的標題和內容

class LxSpider(CrawlSpider):
    name = 'lx'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['http://wz.sun0769.com/html/top/report.shtml']
    # 連接提取器
    link = LinkExtractor(allow=r'page=\d+')
    link_1 = LinkExtractor(allow=r'page=$')
    link_detail = LinkExtractor(allow=r'question/\d+/\d+\.shtml')
    rules = (
        # 實例化一個Rule(規則解析器)對象
        # follow=True: 將連接提取器 繼續 做用到連接提取到的連接所對應的頁面源碼中
        Rule(link_1, callback='parse_item', follow=False),
        Rule(link, callback='parse_item', follow=True),
        Rule(link_detail, callback='parse_item_detail', follow=False),
    )

    # 數據解析,是用來解析連接提取器提取到的連接所對應的頁面
    def parse_item(self, response):
        tr_list = response.xpath('/html/body/div[8]/table[2]//tr')
        for tr in tr_list:
            title = tr.xpath('./td[3]/a/text()').extract_first()
            num = tr.xpath('./td[1]/text()').extract_first()
            item = Item2()
            item['title'] = title
            item['num'] = num
            yield item

    # 解析詳情頁中的新聞內容
    def parse_item_detail(self, response):
        content = ''.join(response.xpath('/html/body/div[9]/table[2]//tr[1]/td//text()').extract())
        num = response.xpath('/html/body/div[9]/table[1]//tr/td[2]/span[2]/text()').extract_first().split(':')[-1]
        item = Item1()
        item['content'] = content
        item['num'] = num
        yield item

注意, 若是想將不一樣頁面的數據統一保存,使用Crawl Spider不是很方便,由於它沒法進行請求傳參.

十, 分佈式

什麼是分佈式?

須要搭建一個由n臺電腦組成的機羣,而後在每一臺電腦中執行同一組程序,讓其對同一個網絡資源進行聯合且分佈的數據爬取,大幅度提高爬取效率

實現方式:

  • scrapy + scrapy_redis組件實現的分佈式.

注意, 原生的scrapy是不能夠實現分佈式的,爲何?

  • 調度器不能夠被分佈式機羣共享
  • 管道不能夠被分佈式機羣共享

scrapy-redis組件的做用是什麼?

  • 提供能夠被共享的管道和調度器

實現流程:

  1. 環境的安裝: pip install scrapy-redis
  2. 建立工程: scrapy startproject proName
  3. cd proName
  4. 建立爬蟲文件
    • 基於Spider
    • 基於Craw Spider
  5. 修改爬蟲文件

    1. 導入:
    from scrapy_redis.spiders import RedisCrawlSpider
    from scrapy_redis.spiders import RedisSpider
    將當前爬蟲類的父類修改成導入的類
    刪除allowed_domains和start_urls
    添加一個redis_key = 'xxx'屬性,表示調度器隊列的名稱
    根據常規形式編寫爬蟲文件後續的代碼
  6. 修改settings.py配置文件

    # 指定管道
    ITEM_PIPELINES = {
     'scrapy_redis.pipelines.RedisPipeline': 400
    }
    
    # 指定調度器
    
    # 增長了一個去重容器類的配置, 做用使用Redis的set集合來存儲請求的指紋數據, 從而實現請求去重的持久化
    DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
    # 使用scrapy-redis組件本身的調度器
    SCHEDULER = "scrapy_redis.scheduler.Scheduler"
    # 配置調度器是否要持久化, 也就是當爬蟲結束了, 要不要清空Redis中請求隊列和去重指紋的set。若是是True, 就表示要持久化存儲, 就不清空數據, 不然清空數據
    SCHEDULER_PERSIST = True
    # 指定redis數據庫
    REDIS_HOST = '127.0.0.1'
    REDIS_PORT = 6379
  7. 修改redis的配置文件redis.windows.conf

    關閉默認綁定
     56行: # bind 127.0.0.1
    關閉保護模式
     75行: protected-mode no
  8. 啓動redis的服務端(攜帶配置文件)和客戶端

  9. 啓動分佈式的程序

    cd 到spiders文件夾內
    scrapy runspider xxx.py
  10. 向調度器的隊列中添加一個起始的url

    隊列是存在於redis中的
    redis的客戶端中: lpush sun www.xxx.com

案例:

# spiders文件中
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from scrapy_redis.spiders import RedisCrawlSpider
from fenbushi.items import FenbushiItem

class FbsSpider(RedisCrawlSpider):
    name = 'fbs'
    # allowed_domains = ['www.xxx.com']
    # start_urls = ['http://www.xxx.com/']
    redis_key = 'sun'  # 可被共享的調度器隊列的名稱,即須要添加起始url的redis中的set
    link = LinkExtractor(allow=r'page=\d+')
    rules = (
        Rule(link, callback='parse_item', follow=True),
    )

    def parse_item(self, response):
        tr_list = response.xpath('/html/body/div[8]/table[2]//tr')
        for tr in tr_list:
            title = tr.xpath('./td[3]/a/text()').extract_first()
            item = FenbushiItem()
            item['title'] = title
            yield item

十一, 增量式

概念: 監測網站數據更新

核心技術: 去重

適合使用增量式的網站:

  • 基於深度爬取: 對爬取過的頁面的url進行記錄(記錄表)
  • 基於非深度爬取: 爬取過的數據對應的數據指紋(記錄表)
    • 數據指紋: 原始數據的一組惟一標識(例如MD5值)

記錄表是以怎樣的形式存在於哪?

推薦使用redis的set充當記錄表

案例:

監測https://www.4567tv.tv/index.php/vod/show/id/1/page/1.html的電影標題與簡介

import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from redis import Redis
from zlsPro.items import ZlsproItem


class ZlsSpider(CrawlSpider):
    name = 'zls'
    start_urls = ['https://www.4567tv.tv/index.php/vod/show/id/1/page/1.html']
    # 解析每一頁的頁碼連接
    link = LinkExtractor(allow=r'1/page/\d+\.html')
    rules = (
        Rule(link, callback='parse_item', follow=False),
    )
    conn = Redis(host='127.0.0.1', port=6379)

    def parse_item(self, response):
        li_list = response.xpath('/html/body/div[1]/div/div/div/div[2]/ul/li')
        for li in li_list:
            detail_url = 'https://www.4567tv.tv' + li.xpath('./div/a/@href').extract_first()
            ex = self.conn.sadd('urls', detail_url)
            if ex == 1:
                # detail_url不存在於redis的set中
                yield scrapy.Request(detail_url, self.parse_detail)
            else:
                # detail_url存在於redis的set中
                print('無數據更新')

    def parse_detail(self, response):
        title = response.xpath('/html/body/div[1]/div/div/div/div[2]/h1/text()').extract_first()
        desc = response.xpath('/html/body/div[1]/div/div/div/div[2]/p[5]/span[3]/text()').extract_first()
        print(title, '是新數據')
        item = ZlsproItem()
        item['title'] = title
        item['desc'] = desc
        yield item

十二, 反爬機制

robots: 無論它..

UA檢測: UA假裝

圖片懶加載: 獲取相應的僞屬性對應的值

驗證碼: 打碼平臺,selenium

cookie: session對象自動記錄cookie

動態加載的數據: 使用抓包工具,獲取動態加載數據對應的url

動態變化的請求參數: 通常隱藏在頁面源碼中

IP檢測: IP代理池

js混淆: js反混淆

js加密: 找到對應的js代碼,使用PyExecJS運行js代碼

相關文章
相關標籤/搜索