58同城 反爬蟲機制及處理

社會主義新碼農發表於2020-08-15

58同城 反爬蟲機制及處理

  1. 字型反爬機制
    問題:

字型反爬也就是自定義字型反爬通過呼叫自定義的ttf檔案來渲染網頁中的文字,而網頁中的文字不再是文字,而是相應的字型編碼,通過複製或者簡單的採集是無法採集到編碼後的文字內容!必須通過程式去處理才能達到採整合本。
例如:也就是通過自定義字型來自定義字元與渲染圖形的對映。比如,字元 a 實際渲染的是 9,那麼如果 HTML 中的數字是 aaa,實際顯示就是 999。

網頁顯示與原始碼出現不一致的情況
這裡58同城將數字(比較敏感的元素,包括價格,面積大小)用自定義的ttf檔案進行了渲染(而且該檔案通過base64進行加密,包含數字到相應的unicode字元之間的對映資訊),從而產生類似於亂碼的情況。

Base64:是從二進位制到字元的過程,可用於在HTTP環境下傳遞較長的標識資訊。採用Base64編碼具有不可讀性,需要解碼後才能閱讀。

處理:
(1)在網頁原始碼中找到相應的base64編碼的檔案資訊,對其進行解碼並儲存為ttf格式檔案
在這裡插入圖片描述

font_face = 'AAEAAAALAIAAAwAwR1NVQiCLJXoAAAE4AA...AAA' # 此次省略
b = base64.b64decode(font_face)  # 對對映表進行解碼
with open('58.ttf', 'wb') as f:
    f.write(b)

(2)可以利用FontCreate軟體得出每一個數字對應unicode字元(對映關係),存入字典中(注意字典中的value也得換成unicode字元)
在這裡插入圖片描述


```python
diction = {'\u9a4b': '\u0030', '\u9f92': '\u0031', '\u993c': '\u0032',
           '\u9ea3': '\u0033', '\u9fa4': '\u0034', '\u9fa5': '\u0035',
           '\u958f': '\u0036', '\u9e3a': '\u0037', '\u9476': '\u0038',
           '\u9f64': '\u0039'}  # 分別是某unicode字元與數字之間的對映

(3)最後以該字典為對照,將爬取到的包含數字的文字,轉換成我們可以看懂的數字。

TIP:通過上述方法並沒有真在知道數字與字元之間的對映關係的規律(函式關係),這個過程的尋找需要花費時間。

  • 頻繁請求爬蟲檢測機制
    問題:
    在爬取過程中,爬蟲模擬瀏覽器對58同城服務端頻繁發出網頁請求以獲取資料,在此過程中,使用同一IP頻繁訪問就會被服務端判定為爬蟲,此時返回網頁會出現人機驗證資訊:
    在這裡插入圖片描述
    處理:
  • 使用IP代理:

我們給瀏覽器設定代理,我們訪問目標網站的請求會先經過代理伺服器,然後由代理伺服器將請求轉化到目標網站,這樣每次使用不同的IP地址訪問,讓伺服器以為不是來自同一使用者的請求,一定程度降低了被發現的風險。

  • 代理IP的來源:

從免費ip代理網站獲取
分為三個部分:分別是獲取ip和埠;對ip、埠進行驗證(判斷ip是否可用);儲存可用IP
優勢:免費
劣勢:大部分免費IP不可用
購買IP代理:
直接利用提過收費方提供的API介面,獲取IP
優勢:花了錢的肯定是有用的,獲取方便,IP存活率高
劣勢:要RMB

# coding:utf-8
import requests
from bs4 import BeautifulSoup
from fake_useragent import UserAgent
import random

ROOT_URL = 'http://www.89ip.cn/index_'

ip_list = []


def judge(ip_port):
    proxies = {'http': 'http://'+ip_port, 'https': 'https://'+ip_port}
    try:
        res = requests.get('https://www.baidu.com', headers=UserAgent.random, proxies=proxies)
    except Exception:
        return False
    else:
        if 200 <= res.status_code < 300:
            # 返回的狀態碼在200到300之間表示請求成功
            return True
        else:
            print('請求失敗')
            return False


# 第三步:從ip_list中隨機獲取一個ip
def get_random_ip():
    while ip_list:
        ip_port = random.choice(ip_list)
        result1 = judge(ip_port)
        if result1 is not False:
            proxies = {'http': 'http://'+ip_port, 'https': 'https://'+ip_port}
            return proxies
        else:
            ip_list.remove(ip_port)
    return None


class IpPool(object):

    def _get_ip(self, n):
        try:
            headers = {'User-Agent': UserAgent.random}
        except AttributeError:
            headers = {
                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
                              'AppleWebKit/537.36 (KHTML, like Gecko) '
                              'Chrome/84.0.4147.105 Safari/537.36 '}
        url = ROOT_URL + '{page}'.format(page=n)
        response = requests.get(url, headers=headers)
        soup = BeautifulSoup(response.text, 'html.parser')
        items = soup.find_all('td')
        for i, j in zip(items[::5], items[1::5]):
            ip_port = i.text.replace('\t', '').replace('\n', '') \
                      + ':' + j.text.replace('\n', '').replace('\t', '')
            ip_list.append(ip_port)

    def run(self):
        for x in range(1, 20):
            self._get_ip(x)
        return get_random_ip()

相關文章