為你的爬蟲添加 IP 池反反爬策略

最近發(fā)現(xiàn)自己之前爬的某個(gè)網(wǎng)站更換了新的網(wǎng)頁設(shè)計(jì),于是重寫了爬蟲,在測試的時(shí)候突然被封了 IP寻仗,雖然說一般網(wǎng)站都不是永久封 IP,但是等不了的我還是嘗試用 IP 池來突破該網(wǎng)站的反爬凡壤。

封面

而就在我測試爬下來的 IP 能不能使用的時(shí)候署尤,某提供 IP 池的網(wǎng)站也把我的 IP 封了!想不到現(xiàn)在的反爬策略已經(jīng)如此激進(jìn)亚侠。

開始之前

首先要清楚一些基本的網(wǎng)絡(luò)狀態(tài)號(hào)曹体。

  • 1XX消息 這一類型的狀態(tài)碼,代表請求已被接受硝烂,需要繼續(xù)處理箕别。(一般很少用)
  • 2XX成功 這一類型的狀態(tài)碼,代表請求已成功被服務(wù)器接收、理解串稀、并接受除抛。(但是未必能按請求返回結(jié)果)
    200 OK 請求成功
    201 Created 請求已經(jīng)被實(shí)現(xiàn),而且有一個(gè)新的資源已經(jīng)依據(jù)請求的需要而建立
    202 Accepted 服務(wù)器已接受請求母截,但尚未處理
  • 3XX重定向 這類狀態(tài)碼代表需要客戶端采取進(jìn)一步的操作才能完成請求到忽。通常,重定向目標(biāo)在本次響應(yīng)的Location域中指明清寇。
    301 Moved Permanently 被請求的資源已永久移動(dòng)到新位置
    302 Found 要求客戶端執(zhí)行臨時(shí)重定向, 原始描述短語為“Moved Temporarily”
  • 4xx客戶端錯(cuò)誤 這類的狀態(tài)碼代表了客戶端看起來可能發(fā)生了錯(cuò)誤喘漏,妨礙了服務(wù)器的處理.
    401 Unauthorized 該狀態(tài)碼表示當(dāng)前請求需要用戶驗(yàn)證
    403 Forbidden 服務(wù)器已經(jīng)理解請求,但是拒絕執(zhí)行它(爬蟲被禁的標(biāo)志)
    404 Not Found 請求失敗华烟,請求所希望得到的資源未被在服務(wù)器上發(fā)現(xiàn)
  • 5xx服務(wù)器錯(cuò)誤 這類狀態(tài)碼代表了服務(wù)器在處理請求的過程中有錯(cuò)誤或者異常狀態(tài)發(fā)生陷遮,也有可能是服務(wù)器意識(shí)到以當(dāng)前的軟硬件資源無法完成對請求的處理.
    500 Internal Server Error 通用錯(cuò)誤消息,服務(wù)器遇到了一個(gè)未曾預(yù)料的狀況垦江,導(dǎo)致了它無法完成對請求的處理帽馋。沒有給出具體錯(cuò)誤信息。
    502 Bad Gateway 作為網(wǎng)關(guān)或 "代理服務(wù)器" 工作的服務(wù)器嘗試執(zhí)行請求時(shí)比吭,從上游服務(wù)器接收到無效的響應(yīng)绽族。
    503 Service Unavailable 由于臨時(shí)的服務(wù)器維護(hù)或者過載,服務(wù)器當(dāng)前無法處理請求衩藤。

在爬蟲過程中吧慢,我們最想看到的狀態(tài)碼是 200,最不想看到的是 403赏表,當(dāng)你看到 403检诗,有相當(dāng)大可能是你的爬蟲被封了。

常見的反爬和反反爬策略

基于 Headers 和 UserAgent 的反爬

這應(yīng)該是最基本的反爬瓢剿,之前的文章提到過一些網(wǎng)站的 robots.txt 會(huì)明確指明哪些 header 名不能訪問網(wǎng)站(比如一些國內(nèi)的網(wǎng)站不會(huì)讓國外某些搜索網(wǎng)站收錄逢慌,因?yàn)檫@只會(huì)增加網(wǎng)站負(fù)載,但是無法帶來真正有用的流量

  • 應(yīng)對方式 隨機(jī)更換 UserAgent间狂」テ茫可以自己寫一個(gè) UserAgent 列表,然后隨機(jī)挑一條作為當(dāng)前爬蟲請求的 UserAgent鉴象,也可以使用已經(jīng)寫好的庫fake_useragent
    安裝使用非常簡單:
# 安裝
pip install fake_useragent
>>> from fake_useragent import UserAgent
>>> ua = UserAgent(verify_ssl=False)
>>> ua.random
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.62 Safari/537.36'

基于用戶行為的反爬

爬蟲除了有英文 Spider 外忙菠,還有一個(gè)英文是 bot,也就是機(jī)器人纺弊,機(jī)器人固定的模式是比較容易識(shí)別的牛欢。爬蟲這個(gè)機(jī)器人最明顯的行為特征就是短期內(nèi)密集多次請求網(wǎng)站數(shù)據(jù)。

  • 應(yīng)對方式1 減少請求數(shù)量淆游,減少請求密度 在 Scrapy 中可以設(shè)置并發(fā)請求的數(shù)量傍睹,也可以設(shè)置下載延遲隔盛。前面提到我爬取的 IP 池網(wǎng)站,就是沒有設(shè)置下載延遲焰望,很快就被網(wǎng)站封了 IP骚亿。
  • 應(yīng)對方式2 變換 IP 通過多個(gè) IP 代理你的請求進(jìn)行爬蟲已亥,繞過同一個(gè) IP 多次請求的反爬熊赖。

多說一句,基于用戶行為能做的除了反爬虑椎,還能精準(zhǔn)推送震鹉,精準(zhǔn)拉黑。精準(zhǔn)推送比如你多次搜索某些關(guān)鍵詞捆姜,在網(wǎng)頁中你會(huì)收到相關(guān)的廣告传趾;精準(zhǔn)拉黑比如你使用百度云的破解插件或者修改版多次后,你會(huì)被限制下載等泥技。

隱藏真實(shí)地址的動(dòng)態(tài)網(wǎng)頁反爬

之前筆者的文章寫過 JS動(dòng)態(tài)加載以及JavaScript void(0)的爬蟲解決方案浆兰,實(shí)際上是動(dòng)態(tài)網(wǎng)頁中最基本的反爬。更高級(jí)的反爬珊豹,會(huì)把請求過程中的 XHR 對象的真實(shí)地址進(jìn)一步隱藏簸呈,如果直接打開該XHR地址,你收到的內(nèi)容可能是一樣的店茶,也可能什么內(nèi)容都沒收到蜕便。

應(yīng)對方式1 下圖中的網(wǎng)址就隱藏了真實(shí)網(wǎng)址,你可能需要去查看請求的頭部信息猜測請求參數(shù)贩幻,或者直接通過發(fā)送相同的頭部信息繞過反爬轿腺。

示例

應(yīng)對方式2 使用 selenium+phantomJS 框架調(diào)用瀏覽器內(nèi)核模擬人瀏覽網(wǎng)站的行為,比如滾動(dòng)鼠標(biāo)丛楚,滑動(dòng)驗(yàn)證碼等來繞過反爬族壳,這種應(yīng)該是比較高級(jí)的反反爬策略了。


IP 池突破反爬策略

平時(shí)為了隱藏自己的網(wǎng)絡(luò)行為趣些,有些人會(huì)使用 VPN 來代理自己的流量决侈,隱藏真實(shí)的IP地址。IP 池也是這個(gè)道理喧务,通過不斷變換請求的 IP 地址赖歌,偽裝出低頻訪問的假象繞過反爬策略。

在 Scrapy 中你需要做的有:

  • 爬取并存儲(chǔ)可用 IP(當(dāng)然功茴,RMB玩家可以直接購買接口使用)
  • 編輯并啟用 IP 池中間件

提供 IP 池的網(wǎng)站有不少庐冯,并且大部分會(huì)提供免費(fèi)易黃版RMB玩家穩(wěn)定版,我使用的是免費(fèi)版坎穿,這里介紹兩個(gè)

https://www.kuaidaili.com
http://www.xicidaili.com/

在爬取中務(wù)必設(shè)置合適的速度展父,否則還沒爬到 IP 自己的先被封了返劲。

IP 池是一個(gè)動(dòng)態(tài)構(gòu)建的倉庫,無論是插入還是取出都必須驗(yàn)證該 IP 的有效性栖茉。如何驗(yàn)證篮绿?Python3 中有一個(gè)輕量的 requests 庫(非標(biāo)準(zhǔn)庫),你可以使用該IP地址請求某個(gè)網(wǎng)站看看返回的狀態(tài)碼是否是 200(有時(shí)候也可能是 3XX 這樣的重定向狀態(tài)碼)吕漂,是則證明 IP 可用亲配,可用來爬取信息,否則直接移除惶凝,不保存吼虎。

示例

最好使用 try-except 避免因?yàn)閳?bào)錯(cuò)退出

import requests
request_url = 'http://wwwbaidu.com'
proxy = {'http':'218.28.58.150:53281'}
try:
    requests.get(url=request_url, proxies=proxy, timeout=5)
except Exception as e:
    print(e)

整體的流程大概是

- 爬取 IP 網(wǎng)站
  驗(yàn)證 IP
  >status == 200 ? 入庫:下一條

- 爬取數(shù)據(jù)
  取出 IP
  驗(yàn)證 IP
  >status == 200 ? 出庫, 執(zhí)行爬蟲:下一條
  未找到可用 IP, 數(shù)據(jù)庫為空 -> 爬取 IP 網(wǎng)站

按照下面的步驟,就大功告成啦苍鲜。

  1. 建立 ipProxy.py 的文件(需要新建數(shù)據(jù)庫表)
  2. middlewares.py 中創(chuàng)建中間件
  3. settings.py 中啟用中間件

ipProxy.py

# 此類用于爬取和存儲(chǔ)IP
import requests
from scrapy.selector import Selector
import pymysql
import time

# 鏈接數(shù)據(jù)庫
conn = pymysql.connect(host="127.0.0.1", user="feson", passwd="feson", db="Spider", charset="utf8")
cursor = conn.cursor()

# UserAgent,這里也可以使用隨機(jī)的
headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36'
}


class GetRandomIp(object):
    # 用于解析網(wǎng)頁
    def parse(self, next_url='/inha/1'):
        """
        Parse Ip List From Site, Transfer to parse_detail
        :param next_url:
        :return: None
        """
        print("Begin Parsing...")
        response = requests.get(url='https://www.kuaidaili.com/free/intr'.format(next_url), headers=headers)
        response = Selector(text=response.text)
        tr_list = response.xpath('//*[@id="list"]/table/tbody/tr/td')
        if tr_list:
            self.parse_detail(tr_list)

        for i in range(20):
            time.sleep(5)
            next_url = 'https://www.kuaidaili.com/free/intr/%d' % i
            if next_url:
                self.parse(next_url)

    def parse_detail(self, tr_list):
        """
        Parse Ip detail from list, transfer to insert into database
        :param tr_list:
        :return: None
        """
        ip = tr_list.xpath('//td[@data-title="IP"]/text()').extract()
        port = tr_list.xpath('//td[@data-title="PORT"]/text()').extract()
        type = tr_list.xpath('//td[@data-title="類型"]/text()').extract()
        speed = tr_list.xpath('//td[@data-title="響應(yīng)速度"]/text()').extract()

        for i in range(len(ip)):
            self.insert_sql(ip[i], port[i], type[i])

    def insert_sql(self, ip, port, type):
        type = type.lower()
        proxy_url = '{0}://{1}:{2}'.format(type, ip, port)
        res = self.check_ip(type, proxy_url)
        print(proxy_url)
        if res:
            cursor.execute(
                "insert proxy_ip(ip, port, type) VALUES('{0}', '{1}', '{2}')".format(
                    ip, port, type
                )
            )
            conn.commit()

    def get_ip(self):
        # 獲取和檢查IP
        sql = "select * from proxy_ip ORDER BY RAND() LIMIT 1"
        cursor.execute(sql)
        ip, port, type = cursor.fetchone()
        conn.commit()

        type = type.lower()
        proxy_url = '{0}://{1}:{2}'.format(type, ip, port)
        res = self.check_ip(type, proxy_url)
        if res:
            return proxy_url
        else:
            self.delete_ip(ip)
            return self.get_ip()

    def check_ip(self, type, proxy_url):
        request_url = 'http://hf.58.com/ershoufang/0'
        try:
            proxy = {type: proxy_url}
            response = requests.get(url=request_url, proxies=proxy, timeout=5)
        except Exception as e:
            print(e)
            return False
        else:
            code = response.status_code
            if code == 200 or code == 302:
                return True
            else:
                print('invalid ip and port')
                return False

    def delete_ip(self, ip):
        sql = """delete from proxy_ip where ip='%s'""" %  ip
        cursor.execute(sql)
        conn.commit()


ip = GetRandomIp()

if __name__ == '__main__':
    ip = GetRandomIp()
    ip.parse()

middlewares.py

import ipProxy
class RandomIpMiddleware(object):
    def process_request(self, request, spider):
        ip_proxy = ipProxy.ip.get_ip()
        request.meta['proxy'] = ip_proxy

settings.py

# 添加以下參數(shù), 沒有就新建條目
...
# Retry many times since proxies often fail
RETRY_TIMES = 3
# Retry on most error codes since proxies fail for different reasons
RETRY_HTTP_CODES = [500, 503, 504, 400, 403, 404, 408]
# Enable or disable downloader middlewares
DOWNLOADER_MIDDLEWARES = {
   'middleware.customUserAgent.RandomUserAgent': 543,
   'finvest.middlewares.RandomIpMiddleware': 520,
}

歡迎關(guān)注公眾號(hào): 程序員的碎碎念

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末思灰,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子混滔,更是在濱河造成了極大的恐慌洒疚,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件坯屿,死亡現(xiàn)場離奇詭異油湖,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)愿伴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門肺魁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人隔节,你說我怎么就攤上這事鹅经。” “怎么了怎诫?”我有些...
    開封第一講書人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵瘾晃,是天一觀的道長。 經(jīng)常有香客問我幻妓,道長蹦误,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任肉津,我火速辦了婚禮强胰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘妹沙。我一直安慰自己偶洋,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開白布距糖。 她就那樣靜靜地躺著玄窝,像睡著了一般牵寺。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上恩脂,一...
    開封第一講書人閱讀 49,772評(píng)論 1 290
  • 那天帽氓,我揣著相機(jī)與錄音,去河邊找鬼俩块。 笑死黎休,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的典阵。 我是一名探鬼主播奋渔,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼镊逝,長吁一口氣:“原來是場噩夢啊……” “哼壮啊!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起撑蒜,我...
    開封第一講書人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬榮一對情侶失蹤歹啼,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后座菠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體狸眼,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年浴滴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了拓萌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡升略,死狀恐怖微王,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情品嚣,我是刑警寧澤炕倘,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站翰撑,受9級(jí)特大地震影響罩旋,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜眶诈,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一涨醋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧逝撬,春花似錦浴骂、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽帐我。三九已至,卻和暖如春愧膀,著一層夾襖步出監(jiān)牢的瞬間拦键,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來泰國打工檩淋, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留芬为,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓蟀悦,卻偏偏與公主長得像媚朦,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子日戈,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348