58同城 反爬虫机制及处理
- 字体反爬机制
问题:
字体反爬也就是自定义字体反爬通过调用自定义的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()