<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          用 Scrapy 爬取 5 秒盾站點,結(jié)果萬萬沒想到,速度可以這么快!

          共 10843字,需瀏覽 22分鐘

           ·

          2024-04-11 15:31


          作者:TheWeiJun
          來源:逆向與爬蟲的故事

          閱讀本文大概需要 12 分鐘。




          在新的一年里,期待大家在技術(shù)征途上不斷突破,獲得更多的成就。今天,我將與大家分享一段令人振奮的故事,通過對 Scrapy 爬蟲的 twisted 源碼高并發(fā)改造,成功沖破 5 秒盾站點的屏障。讓我們一同解鎖這個技術(shù)謎團(tuán),探索爬蟲世界的無限可能。祝愿大家在 2024 年迎來更多快樂,事業(yè)騰飛!

          特別聲明:本公眾號文章只作為學(xué)術(shù)研究,不作為其他不法用途;如有侵權(quán)請聯(lián)系作者刪除。


          立即加星標(biāo)

          每月看好文


           目錄


          一、前言介紹
          二、實戰(zhàn)分析
          三、源碼重寫
          四、性能對比
          五、往期推薦




          一、前言介紹


          大家好,我是 TheWeiJun。我懷揣著對技術(shù)的熱愛,迫不及待要與大家分享一場關(guān)于 Scrapy 爬蟲的技術(shù)奇遇。在這個數(shù)字化飛速發(fā)展的時代,我們時刻面臨新的技術(shù)挑戰(zhàn)。在今天的故事中,我將引領(lǐng)大家穿越 Scrapy 的技術(shù)迷霧,通過 twisted 源碼改造,實現(xiàn)高并發(fā)爬取,成功攻克五秒盾站點的技術(shù)難關(guān)。

          如果你對技術(shù)探險充滿好奇,渴望突破技術(shù)的邊界,不妨關(guān)注我的公眾號。在這里,我們將一同探索未知領(lǐng)域,共同啟程,為 2024 年的技術(shù)征途揭開嶄新的篇章!記得點關(guān)注,一同踏上這場技術(shù)冒險之旅吧!

          二、實戰(zhàn)分析


          1. 首先,我們需要尋找一個使用了 CloudFlare 的網(wǎng)站。然后,創(chuàng)建一個 Scrapy 項目,并編寫以下 Spider 代碼:

          # -*- coding: utf-8 -*-from urllib.parse import urlencodeimport scrapy
          class CloudflareSpider(scrapy.Spider): name = 'cloudflare_spider' def __init__(self): super().__init__() self.headers = { 'authority': 'xxxxx', 'accept': 'application/json, text/plain, */*', 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8', 'cache-control': 'no-cache', 'pragma': 'no-cache', 'referer': 'https://xxxxx/feed', 'sec-ch-ua': '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': '"macOS"', 'sec-fetch-dest': 'empty', 'sec-fetch-mode': 'cors', 'sec-fetch-site': 'same-origin', 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', } self.url = 'https://xxxx/xxx/xxx/' def start_requests(self): for page in range(1, 100): params = { 'page': page, 'posts_to_load': '5', 'sort': 'top', } proxies = { "http": "http://127.0.0.1:59292", "https": "http://127.0.0.1:59292", } full_url = self.url + '?' + urlencode(params) yield scrapy.Request( url=full_url, headers=self.headers, callback=self.parse, dont_filter=True, meta={"proxies": proxies} ) def parse(self, response, **kwargs): print(response.text)

          2. 代碼編寫完成后,讓我們一起來查看整個 Scrapy 項目的結(jié)構(gòu)。以下是項目目錄結(jié)構(gòu)的截圖:

          3. 接著,我們編寫 run_spider.py 文件,并在其中注冊我們想要啟動的 Spider(使用 spider_name 變量)以下是代碼示例:

          from scrapy.crawler import CrawlerProcessfrom scrapy.utils.project import get_project_settings
          def run_spider(): process = CrawlerProcess(get_project_settings()) process.crawl('cloudflare_spider') process.start() if __name__ == "__main__": run_spider()


          4. 通過 run_spider.py 模塊運行爬蟲,可以看到 403 狀態(tài)碼錯誤請求,截圖如下:

          5. 此時,Scrapy的parse 解析函數(shù)可能無法獲取到失敗的 response,因為 Scrapy 默認(rèn)只處理狀態(tài)碼在 200 范圍內(nèi)的請求。為了能夠查閱失敗的請求結(jié)果,我們需要設(shè)置允許通過的狀態(tài)碼參數(shù)。以下是相應(yīng)的代碼設(shè)置:

          HTTPERROR_ALLOWED_CODES = [403]


          6. 接下來講解一下為什么要這么設(shè)置? 我們打開 scrapy 源碼,截圖如下:

          7. 在 spidermiddlewares 中間件中,我們可以觀察到 HttpErrorMiddleware 模塊。當(dāng) Scrapy 啟動時,各個模塊會被注冊到 spidermiddlewares 中間件。現(xiàn)在讓我們深入了解它是如何運行的,以下是相關(guān)代碼截圖:

          總結(jié):觀察上述代碼,我們可以注意到 Scrapy 的作者默認(rèn)會過濾掉狀態(tài)碼在 200 以內(nèi)的請求,因為在作者看來,以 200 開頭的請求都是成功的。然而,如果我們想要自定義允許通過的請求狀態(tài)碼,就需要設(shè)置 HTTPERROR_ALLOWED_CODES。

          8. 我們知道 spider 中間件原理并設(shè)置 403 狀態(tài)碼后,重新運行代碼,截圖如下:

          總結(jié):這張頁面截圖對于那些已經(jīng)接觸過 5 秒盾的 Spider 開發(fā)者來說應(yīng)該不陌生。接下來,我們將使用 tls_client 包來繞過 5 秒盾機(jī)制。

          9.此外,大家應(yīng)該都使用過 download middlewares 中間件。下面我們將在下載器中間件中處理 5 秒盾請求,相關(guān)代碼如下:

          from scrapy.http import HtmlResponsefrom tls_client import Session

          class DownloaderMiddleware(object):
          def __init__(self): self.session: Session = Session( client_identifier="chrome_104" )
          def process_request(self, request, spider): proxies = request.meta.get("proxies") or None headers = request.headers.to_unicode_dict() if request.method == "GET": response = self.session.get( url=request.url, headers=headers, proxy=proxies, timeout_seconds=60, ) else: response = self.session.post( url=request.url, headers=headers, proxy=proxies, timeout_seconds=60, ) return HtmlResponse( url=request.url, status=response.status_code, body=response.content, encoding="utf-8", request=request, )

          10. 將上面的模塊注冊到下載器中間件后,我們啟動爬蟲觀察請求結(jié)果,截圖如下所示:

          總結(jié):盡管 5 秒盾已經(jīng)能夠成功解決并返回結(jié)果,我們卻發(fā)現(xiàn) Scrapy 并沒有充分發(fā)揮 Twisted 的異步機(jī)制。這是因為我們在下載器中間件中處理請求時,實際上是在同步的環(huán)境下運行的。如果我們希望 Scrapy 能夠?qū)崿F(xiàn)高并發(fā),就必須修改 Twisted 的請求模塊。我們可以通過重寫 Twisted 請求組件或者兼容 tls_client 模塊來實現(xiàn)高并發(fā)。在這里,我們選擇后者的方式,以達(dá)到 Scrapy 高并發(fā)的目標(biāo)。接下來,我們將進(jìn)入源碼重寫的環(huán)節(jié)。



          三、源碼重寫


          1. 首先,我們來了解一下 Scrapy 的運行機(jī)制,然后找到相應(yīng)的模塊,并查看 Scrapy 源碼的實現(xiàn)。以下是相應(yīng)的截圖:


          總結(jié):在 Scrapy 啟動時,它通過 downloader_handlers 中的 download_request 方法加載 Twisted 模塊,從而進(jìn)行請求的異步處理。一旦我們獲得了靈感,就可以開始繼承并重寫 Scrapy 的源碼。


          2. 我們繼承重寫 downloader hanlders 中的模塊,重寫后源碼如下:

          """Download handlers for http and https schemes"""
          import loggingfrom time import timefrom urllib.parse import urldefrag
          from tls_client import Sessionfrom twisted.internet import threadsfrom twisted.internet.error import TimeoutErrorfrom scrapy.http import HtmlResponsefrom scrapy.core.downloader.handlers.http11 import HTTP11DownloadHandler
          logger = logging.getLogger(__name__)

          class CloudFlareDownloadHandler(HTTP11DownloadHandler):
          def __init__(self, settings, crawler=None): super().__init__(settings, crawler) self.session: Session = Session( client_identifier="chrome_104" )
          @classmethod def from_crawler(cls, crawler): return cls(crawler.settings, crawler)
          def download_request(self, request, spider): from twisted.internet import reactor timeout = request.meta.get("download_timeout") or 10 # request details url = urldefrag(request.url)[0] start_time = time()
          # Embedding the provided code asynchronously d = threads.deferToThread(self._async_download, request)
          # set download latency d.addCallback(self._cb_latency, request, start_time) # check download timeout self._timeout_cl = reactor.callLater(timeout, d.cancel) d.addBoth(self._cb_timeout, url, timeout) return d
          def _async_download(self, request): timeout = int(request.meta.get("download_timeout")) proxies = request.meta.get("proxies") or None headers = request.headers.to_unicode_dict() if request.method == "GET": response = self.session.get( url=request.url, headers=headers, proxy=proxies, timeout_seconds=timeout, ) else: response = self.session.post( url=request.url, headers=headers, proxy=proxies, timeout_seconds=timeout, ) return HtmlResponse( url=request.url, status=response.status_code, body=response.content, encoding="utf-8", request=request, )
          def _cb_timeout(self, result, url, timeout): if self._timeout_cl.active(): self._timeout_cl.cancel() return result raise TimeoutError(f"Getting {url} took longer than {timeout} seconds.")
          def _cb_latency(self, result, request, start_time): request.meta["download_latency"] = time() - start_time return result

          總結(jié):源碼重寫工作已經(jīng)圓滿完成,此時我們迫不及待地期待著 Scrapy 在高并發(fā)環(huán)境下的表現(xiàn)。懷揣這個疑問,讓我們迅速進(jìn)入性能對比環(huán)節(jié)。在進(jìn)行最后的步驟時,請確保將重寫的代碼注冊到 DOWNLOAD_HANDLERS 中間件模塊。




          四、性能對比


          為了進(jìn)行性能對比,我們按照以下規(guī)則進(jìn)行測試:

          • 執(zhí)行 100 個請求的情況下使用下載器中間件方案。

          • 執(zhí)行 100 個請求的情況下使用 Twisted 源碼重寫方案。

          • 執(zhí)行 500 個請求的情況下使用下載器中間件方案。

          • 執(zhí)行 500 個請求的情況下使用 Twisted 源碼重寫方案。


          1. 閱讀完對比流程后,我們先執(zhí)行下載器中間件方案,scrapy 輸出日志如下:

          2. 接著,在相同的環(huán)境中,執(zhí)行源碼重寫方案,Scrapy 輸出的日志如下:

          總結(jié):通過對比兩張截圖的 elapsed_time_seconds 字段,明顯可以觀察到 Scrapy Twisted 源碼重寫方案在執(zhí)行 100 次請求時,爬取速度提升了 6 倍。為了確保性能對比的權(quán)威性,接下來我們將分別執(zhí)行 500 次請求。


          3. 在執(zhí)行 500 次請求時,仍然首先采用下載器中間件方案,Scrapy 輸出的日志如下:

          4. 緊接著,我們執(zhí)行 500 次請求,采用 twisted 源碼重寫方案,Scrapy 輸出的日志如下:

          總結(jié):通過比較 500 次請求的兩張截圖,我們可以觀察到,在 elapsed_time_seconds 方面,Scrapy Twisted 源碼重寫方案明顯優(yōu)于下載器中間件方案。在同時執(zhí)行 500 次請求的情況下,爬取速度提升約為 9 倍。基于這個結(jié)果,我相信在請求量足夠大的場景下,采用 Scrapy Twisted 源碼重寫方案能夠顯著提升爬取效率。


          建了一個免費的知識星球,掃碼入即可。




          點個在看你最好看


          瀏覽 28
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  操屄视频在线播放 | 日本视频 黄| 日韩成人中文字幕 | 天天天天爽夜夜夜夜爽 | 免费A∨视频 |