常见的反爬机制及处理方式
1、Headers反爬虫 :Cookie、Referer、User-Agent
解决方案: 通过F12获取headers,传给requests.get()方法
2、IP限制 :网站根据IP地址访问频率进行反爬,短时间内限制IP访问
解决方案:
1、构造自己IP代理池,每次访问随机选择代理,经常更新代理池
2、购买开放代理或私密代理IP
3、降低爬取的速度
3、User-Agent限制 :类似于IP限制
解决方案: 构造自己的User-Agent池,每次访问随机选择
4、对响应内容做处理
解决方案: 打印并查看响应内容,用xpath或正则做处理
代理参数-proxies
- 定义
1、定义: 代替你原来的IP地址去对接网络的IP地址。
2、作用: 隐藏自身真实IP,避免被封。
普通代理
- 获取代理IP网站
西刺代理、快代理、全网代理、代理精灵、... ...
- 参数类型
1、语法结构
proxies = {
'协议':'协议://IP:端口号'
}
2、示例
proxies = {
'http':'http://IP:端口号',
'https':'https://IP:端口号'
}
- 示例
使用免费普通代理IP访问测试网站: http://httpbin.org/get
import requests
url = 'http://httpbin.org/get'
headers = {
'User-Agent':'Mozilla/5.0'
}
# 定义代理,在代理IP网站中查找免费代理IP
proxies = {
'http':'http://112.85.164.220:9999',
'https':'https://112.85.164.220:9999'
}
html = requests.get(url,proxies=proxies,headers=headers,timeout=5).text
print(html)
思考: 建立一个自己的代理IP池,随时更新用来抓取网站数据
1、从西刺代理IP网站上,抓取免费代理IP
2、测试抓取的IP,可用的保存在文件中
写一个获取收费开放代理的接口
#到网站中查找API接口文档
1,向api连接发送请求,获取到响应的代理ip地址列表
2,测试每个代理ip,可用的保存,建立自己的代理ip池
import requests
class Ip():
def __init__(self):
#此链接是api接口链接,请自行在ip代理网站购买后生成API链接
self.url = 'http://dev.kdlapi.com/api/getproxy/?orderid=967108906914177&num=100&protocol=2&method=2&an_an=1&sep=1'
self.headers = {'User-Ageng': ''}
def save_ip(self):
html = requests.get(url=self.url, headers=self.headers).text
ip_list = html.split('\r\n')
#print(ip_list) # ip_list:['131.161.239.147:8090', '119.27.177.238:8080']
for ip in ip_list:
# 测试ip,可用的保存到文件
if self.test_ip(ip):
# 返回值为True,把ip写入到文件
with open('ip.txt','a') as f:
f.write(ip + '\n')
def test_ip(self, ip):
proxies = {
'http:': 'http://{}'.format(ip),
'https': 'https://{}'.format(ip)
}
try:
#测试IP访问百度
res = requests.get(url='http://www.baidu.com', proxies=proxies, timeout=2)
#状态码为200则可以使用
if res.status_code == 200:
print(ip, "可用")
return True
except Exception as e:
print(ip, "不可用")
return False
if __name__ == '__main__':
spider = Ip()
spider.save_ip()
私密代理
- 语法格式
1、语法结构
proxies = {
'协议':'协议://用户名:密码@IP:端口号'
}
2、示例
proxies = {
'http':'http://用户名:密码@IP:端口号',
'https':'https://用户名:密码@IP:端口号'
}
示例代码
import requests
url = 'http://httpbin.org/get'
proxies = {
'http':'http://309435365:szayclhp@1.195.160.232:17509',
'https':'https://309435365:szayclhp@1.195.160.232:17509'
}
headers = {
'User-Agent' : 'Mozilla/5.0',
}
html = requests.get(url,proxies=proxies,headers=headers,timeout=5).text
print(html)
控制台抓包
- 打开方式及常用选项
1、打开浏览器,F12打开控制台,找到Network选项卡
2、控制台常用选项
1、Network: 抓取网络数据包
1、ALL: 抓取所有的网络数据包
2、XHR:抓取异步加载的网络数据包
3、JS : 抓取所有的JS文件
2、Sources: 格式化输出并打断点调试JavaScript代码,助于分析爬虫中一些参数
3、Console: 交互模式,可对JavaScript中的代码进行测试
3、抓取具体网络数据包后
1、单击左侧网络数据包地址,进入数据包详情,查看右侧
2、右侧:
1、Headers: 整个请求信息
General、Response Headers、Request Headers、Query String、Form Data
2、Preview: 对响应内容进行预览
3、Response:响应内容
309435365@qq.com
zhanshen001
requests.post()参数
- 适用场景
Post类型请求的网站
- 参数-data
response = requests.post(url,data=data,headers=headers)
# data :post数据(Form表单数据-字典格式)
- 请求方式的特点
# 一般
GET请求 : 参数在URL地址中有显示
POST请求: Form表单提交数据
有道翻译破解案例(post)
- 目标
破解有道翻译接口,抓取翻译结果
# 结果展示
请输入要翻译的词语: elephant
翻译结果: 大象
**************************
请输入要翻译的词语: 喵喵叫
翻译结果: mews
- 实现步骤
1、浏览器F12开启网络抓包,Network-All,页面翻译单词后找Form表单数据
2、在页面中多翻译几个单词,观察Form表单数据变化(有数据是加密字符串)
3、刷新有道翻译页面,抓取并分析JS代码(本地JS加密)
4、找到JS加密算法,用Python按同样方式加密生成加密数据
5、将Form表单数据处理为字典,通过requests.post()的data参数发送
-
具体实现
-
1、开启F12抓包,找到Form表单数据如下:
i: 喵喵叫
from: AUTO
to: AUTO
smartresult: dict
client: fanyideskweb
salt: 15614112641250
sign: 94008208919faa19bd531acde36aac5d
ts: 1561411264125
bv: f4d62a2579ebb44874d7ef93ba47e822
doctype: json
version: 2.1
keyfrom: fanyi.web
action: FY_BY_REALTlME
- 2、在页面中多翻译几个单词,观察Form表单数据变化
salt: 15614112641250
sign: 94008208919faa19bd531acde36aac5d
ts: 1561411264125
bv: f4d62a2579ebb44874d7ef93ba47e822
# 但是bv的值不变
- 3、一般为本地js文件加密,刷新页面,找到js文件并分析JS代码
# 方法1
Network - JS选项 - 搜索关键词salt
# 方法2
控制台右上角 - Search - 搜索salt - 查看文件 - 格式化输出
# 最终找到相关JS文件 : fanyi.min.js
- 4、打开JS文件,分析加密算法,用Python实现
# ts : 经过分析为13位的时间戳,字符串类型
js代码实现: "" + (new Date).getTime()
python实现: str(int(time.time()*1000))
# salt
js代码实现: ts+parseInt(10 * Math.random(), 10);
python实现: ts+str(random.randint(0,9))
# sign(设置断点调试,来查看 e 的值,发现 e 为要翻译的单词)
js代码实现: n.md5("fanyideskweb" + e + salt + "n%A-rKaT5fb[Gy?;N5@Tj")
python实现:
from hashlib import md5
string = ''
s = md5()
s.update(string.encode())
sign = s.hxdigest()
- 5、代码实现
import requests
import time
import random
from hashlib import md5
class Youdao():
def __init__(self):
self.url = 'http://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule'
# self.headers = {'User-Agent':'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36'}
self.headers={
#检查频率最高的三个
"Cookie": "OUTFOX_SEARCH_USER_ID=476269132@10.169.0.83; OUTFOX_SEARCH_USER_ID_NCOO=877493519.1571759; JSESSIONID=aaaeVsnHXQ8lL9QQqIo3w; ___rl__test__cookies=1571128172757",
"Referer": "http://fanyi.youdao.com/",
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36"
}
# 获取ts,salt,sign
def get_ts_salt_sign(self, word):
ts = str(int(time.time() * 1000))
salt = ts + str(random.randint(0, 9))
string = "fanyideskweb" + word + salt + "n%A-rKaT5fb[Gy?;N5@Tj"
s = md5()
s.update(string.encode())
sign = s.hexdigest()
return ts,salt,sign
def attack(self,word):
ts,salt,sign = self.get_ts_salt_sign(word)
# form表单数据
data = {
"i": word,
"from": "AUTO",
"to": "AUTO",
"smartresult": "dict",
"client": "fanyideskweb",
"salt": salt,
"sign": sign,
# "ts": ts,
# "bv": "93ab8a54b571f3ceac16a13fbb95fb1c",
"doctype": "json",
"version": "2.1",
"keyfrom": "fanyi.web",
"action": "FY_BY_REALTlME",
}
#此处为post请求,一定使用reqeusts
# html = requests.post(
# url=self.url,
# data=data,
# headers = self.headers,
# ).text() #返回值为字符串
html = requests.post(
url=self.url,
data=data,
headers = self.headers,
).json()
print(html)
print("翻译结果为:",html['translateResult'][0][0]['tgt'])
def run(self):
word = input("请输入要翻译的单词:")
self.attack(word)
if __name__ == '__main__':
spider = Youdao()
spider.run()
python中正则处理headers和formdata
1、pycharm进入方法 :Ctrl + r ,选中 Regex
2、处理headers和formdata
(.*): (.*)
"$1": "$2",
3、点击 Replace All
民政部网站数据抓取
目标
1、URL: http://www.mca.gov.cn/ - 民政数据 - 行政区划代码
即: http://www.mca.gov.cn/article/sj/xzqh/2019/
2、目标: 抓取最新中华人民共和国县以上行政区划代码
3、政府网站谨慎爬取
实现步骤
- 1、从民政数据网站中提取最新行政区划代码链接
# 特点
1、最新的在上面
2、命名格式: 2019年X月中华人民共和国县以上行政区划代码
#a_list = [<element a at xxx>,<element a at xxx]
a_list = p.xpath('//a[@class="artitlelist"]')
for a in a_list:
#第一种方法
title = a.xpath('./@title')
#第二种方法 这个方法比较稳妥
title = a.get('title')
if title.endswith('行政区划代码'):
href = a.get('href') #提取最新的href
break
2、从二级页面链接中提取真实链接(反爬-响应内容中嵌入JS,指向新的链接)
1、向二级页面链接发请求得到响应内容,并查看嵌入的JS代码
2、正则提取真实的二级页面链接
3、真实链接中提取所需数据
#xpath表达式
//tr[2]/td[2]/a/@href
#提取真实链接的正则表达式
window.location.href="(.*?)"
4、代码实现
import re
import requests
from lxml import etree
class CovSpider():
def __init__(self):
self.url = 'http://www.mca.gov.cn/article/sj/xzqh/2019/'
self.headers = {
'User-Agent': 'Mozilla/4.0 (compatible; MSIE 7.0; AOL 9.5; AOLBuild 4337.35; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)'}
# 获取最新链接
def get_link(self):
# 获取一级页面的连接
one_html = requests.get(url=self.url, headers=self.headers).text
# xpath提取最新链接
p = etree.HTML(one_html)
link_list = p.xpath('//tr[2]/td[2]/a/@href')
link = link_list[0] if link_list else None
if link:
# link为假链接
link = 'http://www.mca.gov.cn' + link
# 通过假链接link来提取真实链接
self.get_real_link(link)
else:
print("提取链接失败!")
# 提取真链接
def get_real_link(self, link):
html = requests.get(url=link, headers=self.headers).content.decode()
# 正则解析提取链接
re_db = 'window.location.href="(.*?)"'
p = re.compile(re_db, re.S)
real_link = p.findall(html)[0]
# 提取数据
self.parse_html(real_link)
# 提取数据
def parse_html(self, link_link):
two_html = requests.get(url=link_link, headers=self.headers).text
p = etree.HTML(two_html)
tr_list = p.xpath('//tr[@height="19"]')
for tr in tr_list:
name = tr.xpath('./td[3]/text()')[0].strip()
code = tr.xpath('./td[2]/text()')[0].strip()
print(name, code)
# 入口函数
def run(self):
self.get_link()
if __name__ == '__main__':
spider = CovSpider()
spider.run()
扩展
1、建立增量爬虫 - 网站有更新时抓取,否则不抓
2、所抓数据存到数据库,按照层级关系分表存储 - 省、市、县表
动态加载数据抓取-Ajax
- 特点
1、右键 -> 查看网页源码中没有具体数据
2、滚动鼠标滑轮或其他动作时加载,或者页面局部刷新
- 抓取
1、F12打开控制台,页面动作抓取网络数据包
2、抓取json文件URL地址
# 控制台中 XHR :异步加载的数据包
# XHR -> QueryStringParameters(查询参数)