網(wǎng)絡(luò)爬蟲Scrapy從入門到進階

Advanced Web Scraping: Bypassing "403 Forbidden," captchas, and more
—— github

我嘗試過x-ray/cheerio, nokogiri等等爬蟲框架,最終還是回到了我的最愛: scrapy钧排。它確實非常直觀唯欣,學(xué)習(xí)曲線友好屁倔。
通過The Scrapy Tutorial中文版)你可以在幾分鐘之內(nèi)上手你的第一只爬蟲。然后除秀,當(dāng)你需要完成一些復(fù)雜的任務(wù)時,你很可能會發(fā)現(xiàn)有內(nèi)置好的、文檔良好的方式來實現(xiàn)它瓢剿。(Scrapy內(nèi)置了許多強大的功能,但Scrapy的框架結(jié)構(gòu)良好悠轩,如果你還不需要某個功能间狂,它就不會影響你。)再者火架,如果你最終需要某些新功能鉴象,例如一個布隆過濾器來去重大量的鏈接,通常只需要簡單地子類化某個組件何鸡,并做點小小的修改就可以了纺弊。

  • 注:作者為了爬蟲道德,在教程中使用了虛擬的目標(biāo)網(wǎng)站Zipru骡男,假想它為一個種子站點淆游。自己嘗試時,修改為自己的需求網(wǎng)址即可隔盛。

新建項目

scrapy startproject zipru_scraper
  • 這會創(chuàng)建如下的目錄結(jié)構(gòu):
└── zipru_scraper
    ├── zipru_scraper
    │   ├── __init__.py
    │   ├── items.py
    │   ├── middlewares.py
    │   ├── pipelines.py
    │   ├── settings.py
    │   └── spiders
    │       └── __init__.py
    └── scrapy.cfg

默認(rèn)情況下,大多數(shù)這些文件實際上不會被使用吮炕,它們的存在只是建議我們以一個合理的方式組織我們的代碼腊脱。當(dāng)前,你只需要考慮zipru_scraper(第一個)作為項目的頂層目錄龙亲,這就是任何scrapy命令應(yīng)該運行的地方陕凹,也是任何相對路徑的根。

添加一個基本爬蟲

  • 現(xiàn)在我們需要添加一個小爬蟲來真正做點什么俱笛。
  • 創(chuàng)建文件zipru_scraper/spiders/zipru_spider.py添加如下內(nèi)容:
import scrapy

class ZipruSpider(scrapy.Spider):
    name = 'zipru'
    start_urls = ['http://zipru.to/torrents.php?category=TV']
  • 我們的小爬蟲繼承了scrapy.Spider捆姜,它內(nèi)置的start_requests()方法會自動遍歷start_urls列表中的鏈接來開始我們的抓取。我們先嘗試一個類似下圖的種子列表頁面迎膜。

    對頁碼審查元素我們會看到:
<a href="/torrents.php?...page=2" title="page 2">2</a>
<a href="/torrents.php?...page=3" title="page 3">3</a>
<a href="/torrents.php?...page=4" title="page 4">4</a>
    def parse(self, response):
        # 從頁面中取出頁碼里包含的鏈接
        for page_url in response.css('a[title ~= page]::attr(href)').extract():
            page_url = response.urljoin(page_url)
            # 將解析出的href里的鏈接自動判斷補全
            yield scrapy.Request(url=page_url, callback=self.parse)
            # 由解析出的url生成新的請求對象
  • 在爬取從start_urls自動開始后,服務(wù)器返回的響應(yīng)會自動傳遞給parse(self, response)方法磕仅。在解析了頁面中的頁碼元素里包含的鏈接后珊豹,我們由每個解析出的url生成新的請求對象簸呈,它們的響應(yīng)的解析方法即回調(diào)函數(shù)還是這個parse方法。只要這些url還沒被處理過店茶,這些請求將被轉(zhuǎn)換為響應(yīng)對象蜕便,仍然饋入parse(self, response)方法(感謝dupe過濾器幫我們自動去重鏈接)。
  • 現(xiàn)在我們的小爬蟲已經(jīng)可以不斷地爬取新的列表頁面了贩幻。但是我們還沒有獲取到實際的信息轿腺。讓我們加上對表格中元素的解析來獲取種子信息。
    def parse(self, response):
        # 從頁面中取出頁碼里包含的鏈接
        for page_url in response.xpath('//a[contains(@title, "page ")]/@href').extract():
            page_url = response.urljoin(page_url)
            yield scrapy.Request(url=page_url, callback=self.parse)

        # 提取種子信息
        for tr in response.css('table.lista2t tr.lista2'):
            tds = tr.css('td')
            link = tds[1].css('a')[0]
            yield {
                'title' : link.css('::attr(title)').extract_first(),
                'url' : response.urljoin(link.css('::attr(href)').extract_first()),
                'date' : tds[2].css('::text').extract_first(),
                'size' : tds[3].css('::text').extract_first(),
                'seeders': int(tds[4].css('::text').extract_first()),
                'leechers': int(tds[5].css('::text').extract_first()),
                'uploader': tds[7].css('::text').extract_first(),
            }
  • 在解析出url生成新的請求后丛楚,我們的小爬蟲會處理當(dāng)前頁面的種子信息族壳,生成一個字典項目,作為我們爬蟲數(shù)據(jù)輸出的一部分趣些。(Scrapy會根據(jù)類型自動判斷我們yield產(chǎn)出的是新的請求還是數(shù)據(jù)信息仿荆。)
    對大多數(shù)網(wǎng)站的數(shù)據(jù)抓取來說,我們的任務(wù)就已經(jīng)完成了坏平。在命令行運行:
scrapy crawl zipru -o torrents.jl

幾分鐘后拢操,一個格式良好的JSON Lines文件torrents.jl就生成了,包含了我們需要的種子信息舶替。相反令境,我們會得到這樣的輸出:

[scrapy.extensions.logstats] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
[scrapy.extensions.telnet] DEBUG: Telnet console listening on 127.0.0.1:6023
[scrapy.core.engine] DEBUG: Crawled (403) <GET http://zipru.to/robots.txt> (referer: None) ['partial']
[scrapy.core.engine] DEBUG: Crawled (403) <GET http://zipru.to/torrents.php?category=TV> (referer: None) ['partial']
[scrapy.spidermiddlewares.httperror] INFO: Ignoring response <403 http://zipru.to/torrents.php?category=TV>: HTTP status code is not handled or not allowed
[scrapy.core.engine] INFO: Closing spider (finished)

這時我們要考慮是否有公開API可以使用,或者耐心分析下問題坎穿。

簡單的問題

  • 我們的第一個請求獲得了一個403響應(yīng)展父,它會被忽略,然后因為我們只給了一個目標(biāo)網(wǎng)址爬蟲停止了玲昧。
  • 如果手動瀏覽器訪問該鏈接顯示正常栖茉,我們可以用tcpdump來比較兩次請求的差異。但我們首先需要檢查的是User Agent參數(shù)孵延。
  • 默認(rèn)情況下吕漂,Scrapy將其標(biāo)識為“Scrapy / 1.3.3(+ http://scrapy.org)”,某些服務(wù)器可能會阻止它或者甚至只是將有限數(shù)量的User Agent列入白名單尘应。
  • 我們可以找到最常用的User Agents惶凝,使用其中之一通常足以繞過基本的防爬措施。這里我們將zipru_scraper/settings.py文件中的
# Crawl responsibly by identifying yourself (and your website) on the user-agent
#USER_AGENT = 'zipru_scraper (+http://www.yourdomain.com)'

替換為

USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36'

為了使我們的爬蟲訪問表現(xiàn)得更像人類的操作犬钢,讓我們降低請求速率(原理上借助AutoThrottle 拓展)苍鲜,在settings.py中繼續(xù)添加

CONCURRENT_REQUESTS = 1
DOWNLOAD_DELAY = 5

此外,我們的爬蟲還會自動遵守robots.txt玷犹,可謂爬蟲界的好公民了』焯希現(xiàn)在運行 scrapy crawl zipru -o torrents.jl 應(yīng)該會有如下輸出:

[scrapy.core.engine] DEBUG: Crawled (200) <GET http://zipru.to/robots.txt> (referer: None)
[scrapy.downloadermiddlewares.redirect] DEBUG: Redirecting (302) to <GET http://zipru.to/threat_defense.php?defense=1&r=78213556> from <GET http://zipru.to/torrents.php?category=TV>
[scrapy.core.engine] DEBUG: Crawled (200) <GET http://zipru.to/threat_defense.php?defense=1&r=78213556> (referer: None) ['partial']
[scrapy.core.engine] INFO: Closing spider (finished)

不錯,有所進展。我們得到兩個200狀態(tài)碼和一個下載器中間件會自動處理的302重定向響應(yīng)坯屿。不幸的是油湖,302將我們指向一個看上去不詳?shù)逆溄?code>threat_defense.php。毫不意外领跛,爬蟲在那沒找到什么有用的信息乏德,爬取終止了。

下載器中間件

  • 在我們深入探索之前了解下Scrapy是如何處理請求和響應(yīng)的很有幫助吠昭。
  • 當(dāng)我們創(chuàng)建基本爬蟲時喊括,生成了scrapy.Request對象,然后它以某種方式轉(zhuǎn)換為對應(yīng)服務(wù)器響應(yīng)的scrapy.Response怎诫。某種方式中的一大部分是下載器中間件的作用瘾晃。
  • 下載器中間件繼承scrapy.downloadermiddlewares.DownloaderMiddleware贷痪,并且同時實現(xiàn)了process_request(request, spider)process_response(request, response, spider)方法幻妓。顧名思義,可以猜得它們是做什么的劫拢。實際上肉津,有一大堆默認(rèn)的下載器中間件在工作。這是標(biāo)準(zhǔn)配置的樣子(當(dāng)然你可以修改它們):
DOWNLOADER_MIDDLEWARES_BASE = {
    'scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware': 100,
    'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware': 300,
    'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware': 350,
    'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware': 400,
    'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': 500,
    'scrapy.downloadermiddlewares.retry.RetryMiddleware': 550,
    'scrapy.downloadermiddlewares.ajaxcrawl.AjaxCrawlMiddleware': 560,
    'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware': 580,
    'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 590,
    'scrapy.downloadermiddlewares.redirect.RedirectMiddleware': 600,
    'scrapy.downloadermiddlewares.cookies.CookiesMiddleware': 700,
    'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 750,
    'scrapy.downloadermiddlewares.stats.DownloaderStats': 850,
    'scrapy.downloadermiddlewares.httpcache.HttpCacheMiddleware': 900,
}
  • 在一個請求到達服務(wù)器的路上舱沧,它依次通過每個使能的中間件的process_request(request, spider)方法妹沙。按照數(shù)字由小到大的順序依次進行,RobotsTxtMiddleware首先處理該請求熟吏,HttpCacheMiddleware最后處理距糖。一旦響應(yīng)被服務(wù)器返回,它也會依次經(jīng)歷這些中間件的process_response(request, response, spider)方法牵寺。這以相反的順序發(fā)生(數(shù)字大的先處理)悍引。也就是說,數(shù)字越大帽氓,越靠近服務(wù)器端趣斤,數(shù)字越小,越接近爬蟲端黎休。
    一個特別簡單的中間件是CookiesMiddleware浓领。它只是簡單地檢查傳入響應(yīng)的Set-Cookie頭,并且持久化cookies势腮。當(dāng)傳出請求時联贩,它會適當(dāng)?shù)卦O(shè)置Cookie頭。當(dāng)然捎拯,由于考慮cookie過期等因素它會復(fù)雜一點泪幌,但是概念是比較清楚的。
    另一個相當(dāng)基礎(chǔ)的中間件是RedirectMiddleware,它只處理3XX重定向座菠。對于所有非3XX狀態(tài)碼的響應(yīng)它一律放行狸眼。當(dāng)有重定向發(fā)生時,它會由服務(wù)器返回的重定向鏈接地址生成一個新的請求浴滴。當(dāng)process_response(request, response, spider)方法返回一個請求對象而不是一個響應(yīng)對象時拓萌,當(dāng)前的響應(yīng)對象會被丟棄,然后一切隨著新請求重新開始升略。之前看到的輸出信息就是如此微王。
    如果你對這么多默認(rèn)的下載器中間件感興趣,可以查看架構(gòu)總覽品嚣。實際上還有很多其它的東西炕倘。但Scrapy偉大的地方在于你不必了解很多,正如不需要知道下載器中間件的存在也能寫出功能足夠的爬蟲翰撑,寫出一個工作良好的下載器中間件也不需要你對其它部分的了解罩旋。

困難的問題

  • 回到我們的小爬蟲,我們發(fā)現(xiàn)我們被重定向到threat_defense.php?defense=1&...鏈接而得不到想要的頁面眶诈。在瀏覽器中查看這個頁面涨醋,會是這樣:

    在被重定向到threat_defense.php?defense=2&...之前是這樣的:

    查看第一個頁面的源碼可以發(fā)現(xiàn),頁面中有一些JavaScript代碼用于構(gòu)造一個特殊的重定向URL與設(shè)置瀏覽器cookies逝撬。
    我們需要解決這兩個問題浴骂,當(dāng)然也需要識別驗證碼并提交答案。如果我們不小心弄錯了宪潮,有時會被重定向到其他的驗證碼頁面溯警,有時我們會終止于這樣的頁面:

    我們需要點擊Click here來開始整個重定向周期。小菜一碟狡相,對吧梯轻!
  • 既然所有的問題都來自于一開始的302重定向,那么順理成章谣光,讓我們在定制的redirect middleware(重定向中間件)里解決它們檩淋。在遇到特殊的302重定向到threat_defense.php之外,我們想要這個中間件仍然像普通重定向中間件一樣工作萄金。當(dāng)它遇到了這個特殊的302蟀悦,我們希望它繞過所有這些防范措施,為會話添加訪問Cookie氧敢,最終請求到原始網(wǎng)頁日戈。如果順利的話,對于我們之前寫的小爬蟲來說孙乖,就不需要關(guān)心這些細(xì)節(jié)浙炼,像過去一樣正常請求即可份氧。
  • 替換zipru_scraper/middlewares.py中的內(nèi)容為
import os, tempfile, time, sys, logging
logger = logging.getLogger(__name__)

import dryscrape
import pytesseract
from PIL import Image

from scrapy.downloadermiddlewares.redirect import RedirectMiddleware

class ThreatDefenceRedirectMiddleware(RedirectMiddleware):
    def _redirect(self, redirected, request, spider, reason):
        # 如果沒有特殊的防范性重定向那就正常工作
        if not self.is_threat_defense_url(redirected.url):
            return super()._redirect(redirected, request, spider, reason)

        logger.debug(f'Zipru threat defense triggered for {request.url}')
        request.cookies = self.bypass_threat_defense(redirected.url)
        request.dont_filter = True # 防止原始鏈接被標(biāo)記為重復(fù)鏈接
        return request

    def is_threat_defense_url(self, url):
        return '://zipru.to/threat_defense.php' in url
  • 我們繼承了RedirectMiddleware中間件,使得我們可以復(fù)用內(nèi)置的重定向處理操作弯屈,而只需要將我們的代碼插入_redirect(redirected, request, spider, reason)方法蜗帜,一旦構(gòu)建了重定向請求,它會被process_response(request, response, spider)方法調(diào)用资厉。對于非防范性重定向厅缺,我們調(diào)用父類的標(biāo)準(zhǔn)方法處理。這里宴偿,我們還未實現(xiàn)bypass_threat_defense(url)方法湘捎,但是它任務(wù)很明確,就是返回訪問cookies窄刘,使得我們可以刷新原始請求的cookies窥妇,而重新處理原始請求。
  • 為了啟用我們的新中間件娩践,要在zipru_scraper/settings.py中添加如下內(nèi)容:
DOWNLOADER_MIDDLEWARES = {
    'scrapy.downloadermiddlewares.redirect.RedirectMiddleware': None,
    'zipru_scraper.middlewares.ThreatDefenceRedirectMiddleware': 600,
}

它禁用了默認(rèn)的重定向中間件活翩,并將我們自己的中間件插入到相同的位置。此外欺矫,我們還需要安裝一些需求包:

pip install dryscrape # headless webkit 無頭webkit
pip install Pillow # image processing 圖像處理
pip install pytesseract # OCR 字符識別

要注意纱新,這些包都具有pip不能處理的外部依賴。如果安裝出錯穆趴,你需要訪問dryscrapePillow以及 pytesseract來獲取安裝指導(dǎo)遇汞。

  • 接下來未妹,我們只需要實現(xiàn)bypass_thread_defense(url)。我們可以解析JavaScript來獲取需要的變量空入,并在python中重建邏輯络它,不過那顯得瑣碎而麻煩。讓我們選擇笨拙而簡單的做法歪赢,使用無頭webkit實例化戳。這有不少選擇,不過我獨愛dryscrape(之前安裝的)埋凯。
  • 首先点楼,在我們的中間件構(gòu)造器里初始化一個dryscrape會話。
    def __init__(self, settings):
        super().__init__(settings)

        # start xvfb to support headless scraping
        if 'linux' in sys.platform:
            dryscrape.start_xvfb()

        self.dryscrape_session = dryscrape.Session(base_url='http://zipru.to')

你可以把這個會話當(dāng)作一個瀏覽器標(biāo)簽白对,它會做所有瀏覽器通常所做的事(如獲取外部資源掠廓,獲取腳本)。我們可以在選項卡中導(dǎo)航到新的URL甩恼,點擊按鈕蟀瞧,輸入文本以及做其它各類事務(wù)沉颂。Scrapy支持請求和項目處理的并發(fā),但響應(yīng)的處理是單線程的悦污。這意味著我們可以使用這個單獨的dryscrape會話铸屉,而不用擔(dān)心線程安全。

  • 現(xiàn)在我們來看一下繞過服務(wù)器防御的基本邏輯切端。
    def bypass_threat_defense(self, url=None):
        # 有確實的url則訪問
        if url:
            self.dryscrape_session.visit(url)

        # 如果有驗證碼則處理
        captcha_images = self.dryscrape_session.css('img[src *= captcha]')
        if len(captcha_images) > 0:
            return self.solve_captcha(captcha_images[0])

        # 點擊可能存在的重試鏈接
        retry_links = self.dryscrape_session.css('a[href *= threat_defense]')
        if len(retry_links) > 0:
            return self.bypass_threat_defense(retry_links[0].get_attr('href'))

        # 否則的話抬探,我們是在一個重定向頁面上,等待重定向后再次嘗試
        self.wait_for_redirect()
        return self.bypass_threat_defense()

    def wait_for_redirect(self, url = None, wait = 0.1, timeout=10):
        url = url or self.dryscrape_session.url()
        for i in range(int(timeout//wait)):
            time.sleep(wait)
            # 如果url發(fā)生變化則返回
            if self.dryscrape_session.url() != url:
                return self.dryscrape_session.url()
        logger.error(f'Maybe {self.dryscrape_session.url()} isn\'t a redirect URL?')
        raise Exception('Timed out on the zipru redirect page.')
  • 這里我們處理了在瀏覽器訪問時可能遇到的各種情況帆赢,并且做了一個正常人類會做出的操作。任何時刻采取的行動取決于當(dāng)前的頁面椰于,代碼以一種優(yōu)雅的方式順序處理變化的情況怠益。
  • 最后一個謎題是解決驗證碼。有不少解決驗證碼服務(wù)的API供你在緊要關(guān)頭使用瘾婿,但是這里的驗證碼足夠簡單我們可以用OCR來解決它蜻牢。使用pytesseract做字符識別,我們最終可以添加solve_captcha(img)方法來完善我們的bypass_threat_defense()偏陪。
    def solve_captcha(self, img, width=1280, height=800):
        # 對當(dāng)前頁面截圖
        self.dryscrape_session.set_viewport_size(width, height)
        filename = tempfile.mktemp('.png')
        self.dryscrape_session.render(filename, width, height)

        # 注入javascript代碼來找到驗證碼圖片的邊界
        js = 'document.querySelector("img[src *= captcha]").getBoundingClientRect()'
        rect = self.dryscrape_session.eval_script(js)
        box = (int(rect['left']), int(rect['top']), int(rect['right']), int(rect['bottom']))

        # 解決截圖中的驗證碼
        image = Image.open(filename)
        os.unlink(filename)
        captcha_image = image.crop(box)
        captcha = pytesseract.image_to_string(captcha_image)
        logger.debug(f'Solved the Zipru captcha: "{captcha}"')

        # 提交驗證碼結(jié)果
        input = self.dryscrape_session.xpath('//input[@id = "solve_string"]')[0]
        input.set(captcha)
        button = self.dryscrape_session.xpath('//button[@id = "button_submit"]')[0]
        url = self.dryscrape_session.url()
        button.click()

        # 如果我們被重定向到一個防御的URL抢呆,重試
        if self.is_threat_defense_url(self.wait_for_redirect(url)):
            return self.bypass_threat_defense()

        # 否則就可以返回當(dāng)前的cookies構(gòu)成的字典
        cookies = {}
        for cookie_string in self.dryscrape_session.cookies():
            if 'domain=zipru.to' in cookie_string:
                key, value = cookie_string.split(';')[0].split('=')
                cookies[key] = value
        return cookies

可以看到,如果驗證碼解析失敗笛谦,我們會回到bypass_threat_defense()抱虐。這樣我們擁有多次嘗試的機會,直到成功一次饥脑。
看起來我們的爬蟲應(yīng)該成功了恳邀,可是它陷入了無限循環(huán)中:

[scrapy.core.engine] DEBUG: Crawled (200) <GET http://zipru.to/robots.txt> (referer: None)
[zipru_scraper.middlewares] DEBUG: Zipru threat defense triggered for http://zipru.to/torrents.php?category=TV
[zipru_scraper.middlewares] DEBUG: Solved the Zipru captcha: "UJM39"
[zipru_scraper.middlewares] DEBUG: Zipru threat defense triggered for http://zipru.to/torrents.php?category=TV
[zipru_scraper.middlewares] DEBUG: Solved the Zipru captcha: "TQ9OG"
[zipru_scraper.middlewares] DEBUG: Zipru threat defense triggered for http://zipru.to/torrents.php?category=TV
[zipru_scraper.middlewares] DEBUG: Solved the Zipru captcha: "KH9A8"
...

看起來我們的中間件至少成功解決了驗證碼,然后重新發(fā)起請求灶轰。問題在于新的請求重又觸發(fā)了防御機制谣沸。我一開以為bug在解析與添加cookies,可再三檢查無果笋颤。這是另一個“唯一可能不同的東西是請求頭”的情況乳附。
Scrapy和dryscrape的請求頭顯然都繞過了觸發(fā)403的第一層過濾器,因為我們沒有得到任何403響應(yīng)伴澄。但這肯定是因為某種請求頭的差異造成的問題赋除。我的猜測是,其中一個加密的訪問Cookie包含了完整的原始訪問請求頭的哈希值秉版,如果兩次請求頭不匹配贤重,將觸發(fā)威脅防御機制。這里的意圖可能是防止某人直接將瀏覽器的cookies復(fù)制到爬蟲中清焕,但也只是增加了點小麻煩并蝗。

  • 所以讓我們在zipru_scraper/settings.py明確指定我們的請求頭:
DEFAULT_REQUEST_HEADERS = {
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    'User-Agent': USER_AGENT,
    'Connection': 'Keep-Alive',
    'Accept-Encoding': 'gzip, deflate',
    'Accept-Language': 'en-US,*',
}

注意這里祭犯,我們顯式地使用之前定義的USER_AGENT賦值給User-Agent,雖然它已經(jīng)被用戶代理中間件自動添加滚停,但是這樣做會便于我們復(fù)制請求頭到dryscrape中沃粗。下面修改我們的ThreatDefenceRedirectMiddleware的初始化函數(shù)為:

def __init__(self, settings):
    super().__init__(settings)

    # start xvfb to support headless scraping
    if 'linux' in sys.platform:
        dryscrape.start_xvfb()

    self.dryscrape_session = dryscrape.Session(base_url='http://zipru.to')
    for key, value in settings['DEFAULT_REQUEST_HEADERS'].items():
        # seems to be a bug with how webkit-server handles accept-encoding
        if key.lower() != 'accept-encoding':
            self.dryscrape_session.set_header(key, value)

現(xiàn)在scrapy crawl zipru -o torrents.jl命令行運行,成功了键畴!數(shù)據(jù)流不斷涌出最盅!并且都記錄到了我們的torrents.jl文件里。

總結(jié)

  • 我們成功應(yīng)對了
    1. User agent 過濾
    2. 模糊的JavaScript重定向
    3. 驗證碼
    4. 請求頭一致性檢驗
  • 雖然我們的目標(biāo)網(wǎng)站Zipru是虛構(gòu)的起惕,但是原理是通用的涡贱,希望對你的爬蟲探險有所幫助!
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末惹想,一起剝皮案震驚了整個濱河市问词,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌嘀粱,老刑警劉巖激挪,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異锋叨,居然都是意外死亡垄分,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進店門娃磺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來薄湿,“玉大人,你說我怎么就攤上這事豌鸡『侔悖” “怎么了?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵涯冠,是天一觀的道長。 經(jīng)常有香客問我逼庞,道長蛇更,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任赛糟,我火速辦了婚禮派任,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘璧南。我一直安慰自己掌逛,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布司倚。 她就那樣靜靜地躺著豆混,像睡著了一般篓像。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上皿伺,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天员辩,我揣著相機與錄音,去河邊找鬼鸵鸥。 笑死奠滑,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的妒穴。 我是一名探鬼主播宋税,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼讼油!你這毒婦竟也來了杰赛?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤汁讼,失蹤者是張志新(化名)和其女友劉穎淆攻,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嘿架,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡瓶珊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了耸彪。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片伞芹。...
    茶點故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖蝉娜,靈堂內(nèi)的尸體忽然破棺而出唱较,到底是詐尸還是另有隱情,我是刑警寧澤召川,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布南缓,位于F島的核電站,受9級特大地震影響荧呐,放射性物質(zhì)發(fā)生泄漏汉形。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一倍阐、第九天 我趴在偏房一處隱蔽的房頂上張望概疆。 院中可真熱鬧,春花似錦峰搪、人聲如沸岔冀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽使套。三九已至罐呼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間童漩,已是汗流浹背弄贿。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留矫膨,地道東北人差凹。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像侧馅,于是被迫代替她去往敵國和親危尿。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,086評論 2 355

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