1. 程式人生 > >Scrapy突破反爬蟲的限制

Scrapy突破反爬蟲的限制

分享圖片 dmi 賬號 dai 執行 官方文檔 百分比 自動調整 本機ip

7-1 爬蟲和反爬的對抗過程以及策略
基本概念
爬蟲:自動獲取網站數據的程序,關鍵是批量的獲取
反爬蟲:使用技術手段防止爬蟲程序的方法
誤傷:反爬技術將普通用戶識別為爬蟲,如果誤傷過高,效果再好也不能用
一般ip地址禁止是不太可能被使用的
成本:反爬蟲需要的人力和機器成本
攔截:成功攔截爬蟲,一般攔截率越高,誤傷率越高

初級爬蟲:簡單粗暴,不管服務器壓力,容易弄掛網站
數據保護:
失控的爬蟲:由於某些情況下,忘記或者無法關閉的爬蟲
商業競爭對手

爬蟲和反爬蟲對抗過程

技術分享圖片

挺有趣的過程

7-2 scrapy架構源碼分析

技術分享圖片

Scrapy Engine: 這是引擎,負責Spiders、ItemPipeline、Downloader、Scheduler中間的通訊,信號、數據傳遞等等!(像不像人的身體?)

Scheduler(調度器): 它負責接受引擎發送過來的requests請求,並按照一定的方式進行整理排列,入隊、並等待Scrapy Engine(引擎)來請求時,交給引擎。

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

Spiders:它負責處理所有Responses,從中分析提取數據,獲取Item字段需要的數據,並將需要跟進的URL提交給引擎,再次進入Scheduler(調度器),

Item Pipeline:它負責處理Spiders中獲取到的Item,並進行處理,比如去重,持久化存儲(存數據庫,寫入文件,總之就是保存數據用的)

Downloader Middlewares(下載中間件):你可以當作是一個可以自定義擴展下載功能的組件

Spider Middlewares(Spider中間件):你可以理解為是一個可以自定擴展和操作引擎和Spiders中間‘通信‘的功能組件(比如進入Spiders的Responses;和從Spiders出去的Requests)

數據在整個Scrapy的流向:

程序運行的時候,

引擎:Hi!Spider, 你要處理哪一個網站?

Spiders:我要處理23wx.com

引擎:你把第一個需要的處理的URL給我吧。

Spiders:給你第一個URL是XXXXXXX.com

引擎:Hi!調度器,我這有request你幫我排序入隊一下。

調度器:好的,正在處理你等一下。

引擎:Hi!調度器,把你處理好的request給我,

調度器:給你,這是我處理好的request

引擎:Hi!下載器,你按照下載中間件的設置幫我下載一下這個request

下載器:好的!給你,這是下載好的東西。(如果失敗:不好意思,這個request下載失敗,然後引擎告訴調度器,這個request下載失敗了,你記錄一下,我們待會兒再下載。)

引擎:Hi!Spiders,這是下載好的東西,並且已經按照Spider中間件處理過了,你處理一下(註意!這兒responses默認是交給def parse這個函數處理的)

Spiders:(處理完畢數據之後對於需要跟進的URL),Hi!引擎,這是我需要跟進的URL,將它的responses交給函數 def xxxx(self, responses)處理。還有這是我獲取到的Item。

引擎:Hi !Item Pipeline 我這兒有個item你幫我處理一下!調度器!這是我需要的URL你幫我處理下。然後從第四步開始循環,直到獲取到你需要的信息

鏈接:https://cuiqingcai.com/3472.html

技術分享圖片

Scrapy中的數據流由執行引擎控制,如下所示:

  1. Engine獲得從爬行器中爬行的初始請求。
  2. Engine在調度程序中調度請求,並請求下一次抓取請求。
  3. 調度程序將下一個請求返回到引擎。
  4. 引擎將請求發送到下載器,通過下載器中間件(請參閱process_request())。
  5. 頁面下載完成後,下載器生成一個響應(帶有該頁面)並將其發送給引擎,通過下載器中間件(請參閱process_response())。
  6. 引擎從下載加載程序接收響應,並將其發送給Spider進行處理,並通過Spider中間件(請參閱process_spider_input())。
  7. Spider處理響應,並向引擎返回報廢的項和新請求(要跟蹤的),通過Spider中間件(請參閱process_spider_output())。
  8. 引擎將已處理的項目發送到項目管道,然後將已處理的請求發送到調度程序,並請求可能的下一個請求進行抓取。
  9. 這個過程重復(從第1步),直到調度程序不再發出請求。



鏈接:http://www.imooc.com/article/254496

7-3 Requests和Response介紹

可在官方文檔查看參數和詳細用法:https://scrapy-chs.readthedocs.io/zh_CN/latest/topics/request-response.html

一個 request 對象代表一個HTTP請求,一般來講, HTTP請求是由Spider產生並被Downloader處理進而生成一個 response

class scrapy.http.Request(url[, callback, method=‘GET‘, headers, body, cookies, meta, encoding=‘utf-8‘, priority=0, dont_filter=False, errback]

參數:

url(string) - 此請求的網址
callback(callable) - 將使用此請求的響應(一旦下載)作為其第一個參數調用的函數。有關更多信息,請參閱下面的將附加數據傳遞給回調函數。如果請求沒有指定回調,parse()將使用spider的 方法。請註意,如果在處理期間引發異常,則會調用errback。
method(string) - 此請求的HTTP方法。默認為’GET’。
meta(dict) - 屬性的初始值Request.meta。如果給定,在此參數中傳遞的dict將被淺復制。
body(str或unicode) - 請求體。如果unicode傳遞了a,那麽它被編碼為 str使用傳遞的編碼(默認為utf-8)。如果 body沒有給出,則存儲一個空字符串。不管這個參數的類型,存儲的最終值將是一個str(不會是unicode或None)。
headers(dict) - 這個請求的頭。dict值可以是字符串(對於單值標頭)或列表(對於多值標頭)。如果 None作為值傳遞,則不會發送HTTP頭。
cookie(dict或list) - 請求cookie。這些可以以兩種形式發送。

使用dict:
request_with_cookies = Request(url="http://www.example.com",
cookies={‘currency‘: ‘USD‘, ‘country‘: ‘UY‘})

使用列表:
request_with_cookies = Request(url="http://www.example.com",
cookies=[{‘name‘: ‘currency‘,
‘value‘: ‘USD‘,
‘domain‘: ‘example.com‘,
‘path‘: ‘/currency‘}])

後一種形式允許定制 cookie的屬性domain和path屬性。這只有在保存Cookie用於以後的請求時才有用。

當某些網站返回Cookie(在響應中)時,這些Cookie會存儲在該域的Cookie中,並在將來的請求中再次發送。這是任何常規網絡瀏覽器的典型行為。但是,如果由於某種原因,您想要避免與現有Cookie合並,您可以通過將dont_merge_cookies關鍵字設置為True 來指示Scrapy如此操作 Request.meta。

不合並Cookie的請求示例:

request_with_cookies = Request(url="http://www.example.com",
cookies={‘currency‘: ‘USD‘, ‘country‘: ‘UY‘},
meta={‘dont_merge_cookies‘: True})

encoding(string) - 此請求的編碼(默認為’utf-8’)。此編碼將用於對URL進行百分比編碼,並將正文轉換為str(如果給定unicode)。
priority(int) - 此請求的優先級(默認為0)。調度器使用優先級來定義用於處理請求的順序。具有較高優先級值的請求將較早執行。允許負值以指示相對低優先級。
dont_filter(boolean) - 表示此請求不應由調度程序過濾。當您想要多次執行相同的請求時忽略重復過濾器時使用。小心使用它,或者你會進入爬行循環。默認為False。
errback(callable) - 如果在處理請求時引發任何異常,將調用的函數。這包括失敗的404 HTTP錯誤等頁面。它接收一個Twisted Failure實例作為第一個參數。有關更多信息,請參閱使用errbacks在請求處理中捕獲異常。

class scrapy.http.Response(url[, status=200, headers=None, body=b‘‘, flags=None, request=None])
一個Response對象表示的HTTP響應,這通常是下載(由下載),並供給到爬蟲進行處理。

參數:

url(string) - 此響應的URL
status(integer) - 響應的HTTP狀態。默認為200。
headers(dict) - 這個響應的頭。dict值可以是字符串(對於單值標頭)或列表(對於多值標頭)。
body(str) - 響應體。它必須是str,而不是unicode,除非你使用一個編碼感知響應子類,如 TextResponse。
flags(list) - 是一個包含屬性初始值的 Response.flags列表。如果給定,列表將被淺復制。
request(Requestobject) - 屬性的初始值Response.request。這代表Request生成此響應。
url
包含響應的URL的字符串。

此屬性為只讀。更改響應使用的URL replace()。

status
表示響應的HTTP狀態的整數。示例:200, 404。

headers
包含響應標題的類字典對象。可以使用get()返回具有指定名稱的第一個標頭值或getlist()返回具有指定名稱的所有標頭值來訪問值。例如,此調用會為您提供標題中的所有Cookie:

response.headers.getlist(‘Set-Cookie‘)
1
body
本回復的正文。記住Response.body總是一個字節對象。如果你想unicode版本使用 TextResponse.text(只在TextResponse 和子類中可用)。

此屬性為只讀。更改響應使用的主體 replace()。

request
Request生成此響應的對象。在響應和請求通過所有下載中間件後,此屬性在Scrapy引擎中分配。特別地,這意味著:

HTTP重定向將導致將原始請求(重定向之前的URL)分配給重定向響應(重定向後具有最終URL)。
Response.request.url並不總是等於Response.url
此屬性僅在爬蟲程序代碼和 Spider Middleware中可用,但不能在Downloader Middleware中使用(盡管您有通過其他方式可用的請求)和處理程序response_downloaded。

meta
的快捷方式Request.meta的屬性 Response.request對象(即self.request.meta)。

與Response.request屬性不同,Response.meta 屬性沿重定向和重試傳播,因此您將獲得Request.meta從您的爬蟲發送的原始屬性。

7-4 通過downloadmiddleware隨機更換user-agent

 1 #middlewares.py文件
 2 from fake_useragent import UserAgent #這是一個隨機UserAgent的包,裏面有很多UserAgent
 3 class RandomUserAgentMiddleware(object):
 4     def __init__(self, crawler):
 5         super(RandomUserAgentMiddleware, self).__init__()
 6 
 7         self.ua = UserAgent()
 8         self.ua_type = crawler.settings.get(RANDOM_UA_TYPE, random) #從setting文件中讀取RANDOM_UA_TYPE值
 9 
10     @classmethod
11     def from_crawler(cls, crawler):
12         return cls(crawler)
13 
14     def process_request(self, request, spider):
15         def get_ua():
16             ‘‘‘Gets random UA based on the type setting (random, firefox…)‘‘‘
17             return getattr(self.ua, self.ua_type) 
18 
19         #  用來進行測試的 user_agent_random=get_ua() 
20         request.headers.setdefault(User-Agent, user_agent_random) #這樣就是實現了User-Agent的隨即變換
#settings.py文件
DOWNLOADER_MIDDLEWARES = {
    ArticleSpider.middlewares.RandomUserAgentMiddleware: 543,
    scrapy.downloadermiddlewares.useragent.UserAgentMiddleware:None#這裏要設置原來的scrapy的useragent為None,否者會被覆蓋掉
}
RANDOM_UA_TYPE=random

7-5 scrapy實現ip代理池

百度輸入:本機ip地址,就可以知道自己的ip地址
ip代理:會讓我們的輸入

代理服務器的設置:西刺免費代理ip(不穩定)
設置ip代理池(要多個代理ip,這樣才不怕被封)

先設置一個表 保存數據

技術分享圖片

在ArticleSpider下新建一個python package tools ,

然後在裏面新建一個crawl_xici_ip.py ,這樣子我們就可以選取到可用的代理ip和端口

# -*- coding: utf-8 -*-
__author__ = bobby
import requests
from scrapy.selector import Selector
import MySQLdb
conn = MySQLdb.connect(
    host = "127.0.0.1",
    port = 3306,
    user = "root",
    passwd = "123456",
    db = "article_spider",
    charset = "utf8"
)
cursor = conn.cursor()
def craw_ips():
    #獲取西刺的免費ip代理
    headers = {
        "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36"
    }
    for i in range(1568):
        re = requests.get("https://www.xicidaili.com/nn/{0}".format(i),headers=headers)

        selector = Selector(text=re.text)
        all_trs = selector.css("#ip_list tr")

        ip_list =[]
        for tr in all_trs[1:]: #因為第一個是表頭
            speed_str = tr.css(".bar::attr(title)").extract()[0]
            if speed_str:
                speed = float(speed_str.split("")[0])
            all_texts = tr.css("td::text").extract()

            ip = all_texts[0]
            port = all_texts[1]
            proxy_type = all_texts[5]

            ip_list.append((ip,port,proxy_type,speed))

        for ip_info in ip_list:
            cursor.execute(
                """
                insert proxy_ip(ip,port,speed,proxy_type)VALUES
                ({0},{1},{2},HTTP)
                """.format(ip_info[0],ip_info[1],ip_info[3])
                )
            conn.commit()

class GetIP(object):
    #從數據庫中刪除無效的ip
    def delete_ip(self,ip):
        delete_sql = """
            delete from proxy_ip where ip={0}
            """.format(ip)
        cursor.execute(delete_sql)
        conn.commit()
        return True
    def judge_ip(self,ip,port):
        #判斷ip是否可用
        http_url = "http://www.baidu.com"
        proxy_url = "http://{0}:{1}".format(ip,port)
        try:
            proxy_dict ={
                "http":proxy_url

            }
            response = requests.get(http_url,proxies=proxy_dict)
        except Exception as e:
            print("invalid ip and port")
            self.delete_ip(ip)
            return False
        else:
            code = response.status_code
            if code>=200 and code<300:
                print("effective ip")
                return True
            else:
                print("invalid ip and port")
                self.delete_ip(ip)
                return False
    def get_random_ip(self):
        #隨機獲取
        random_sql = """
            SELECT ip,port FROM proxy_ip
            ORDER BY RAND()
            LIMIT 1
            """
        result = cursor.execute(random_sql)
        for ip_info in cursor.fetchall():
            ip = ip_info[0]
            port = ip_info[1]
            judge_re = self.judge_ip(ip,port)
            if judge_re:
                return "http://{0}:{1}".format(ip,port)
            else:
                return self.get_random_ip()





#print(craw_ips())
if __name__ == "__main__":
           
    get_ip = GetIP()
    get_ip.get_random_ip()

在middlewares中


from tools.crawl_xici_ip import GetIP
class RandomProxyMiddleware(object):
    #更換user-agent
    def process_request(self,request,spider):
        get_ip = GetIP()
        request.meta["proxy"] = get_ip.get_random_ip()


7-6 雲打碼實現驗證碼識別

驗證碼識別方法
1.編碼實現(tesseract-orc)(識別率比較低)
2.在線打碼(識別率90%以上)
3.人工打碼

在線打碼:雲打碼
用戶註冊,開發者賬號註冊


7-7 cookie禁用、自動限速、自定義spider的settings

settings
COOKIES_ENABLED = FALSE 這樣子就是禁用
自動限速
可以在zhihu.py,lagou.py,jobbole.py中添加
custom_settings
={
COOKIES_ENABLED = TRUE /FALSE 表明是否禁用cookies,TRUE表示不禁用
}

DOWNLOAD_DELAY
更友好的對待網站,而不使用默認的下載延遲0。
自動調整scrapy來優化下載速度,使得用戶不用調節下載延遲及並發請求數來找到優化的值。 用戶只需指定允許的最大並發請求數,剩下的都交給擴展來完成。
AUTOTHROTTLE_ENABLED
默認: False

啟用AutoThrottle擴展。

AUTOTHROTTLE_START_DELAY
默認: 5.0

初始下載延遲(單位:秒)。

AUTOTHROTTLE_MAX_DELAY
默認: 60.0

在高延遲情況下最大的下載延遲(單位秒)。

AUTOTHROTTLE_DEBUG
默認: False

Scrapy突破反爬蟲的限制