(声明一下:刷阅读数可耻。)
上一篇文章介绍了博客爬取的思路,本文将介绍伪装的思路,尝试让爬虫模拟正常人为的操作。
思路
- IP代理:正常情况下,大多数的请求应来自不同IP,因此进行IP代理是十分必要的(由于没有合适的工具,因此本文不涉及IP代理);
- 请求头伪装:每个浏览器发出的请求头信息各不相同,特别是“Accept”、“Accept-Encoding”、“Accept-Language”、“User-Agent”等字段,通过收集各个浏览器请求头的信息,在爬虫发出请求时随机选中一个;
- 请求头“Referer”字段:Referer字段用于记录访问当前页面是从哪个页面跳转过来的。对于正常行为,大多是通过点击页面上的链接进入下一页面。因此,在访问文章时,应设置Referer字段的值为文章列表页的地址。
- Cookie:发起请求后,服务器的响应报文会携带Cookie,用于记录信息。因此启用Cookie功能,保存这些Cookie才是正常的行为。
- 访问文章:正常情况下,通过文章列表访问文章时,是随机访问的,所以访问顺序是乱序的,而且也不一定会把所有文章访问一遍(注:访问文章列表页也应该是随机的);
- 请求资源:浏览器请求一个页面后,解析页面内容,发现有js、css、图片、字体等资源需要引入,会分别发起请求去获取相应的资源(通过分析发现,CSDN网站进行资源分离,HTML文件与其它资源文件不在同一域下,因此本文未作请求资源的处理);
- 访问间隔:正常情况下,访问每篇文章有一定的操作间隔;
- 压缩格式:目前,大部分的浏览器都支持数据压缩进行传输,以优化性能。
实现
# -*-coding:utf-8-*-
"""
博客访问量
@version 2.0
@requires Python 3.6.4
@author 范围兄 <ambit_tsai@qq.com>
"""
from urllib import request
from http.cookiejar import CookieJar
from bs4 import BeautifulSoup
from time import sleep
import random
import gzip
# 博客列表
BLOG_LIST = [
'ambit_tsai'
]
# 爬取间隔
CRAWL_INTERVAL = 50
# 请求头部列表
HEADER_LIST = [{
# Chrome 65
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'zh-CN,zh;q=0.9',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36',
'Cache-Control': 'max-age=0',
'Upgrade-Insecure-Requests': '1',
'Connection': 'keep-alive'
}, {
# Firefox 60
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0',
'Upgrade-Insecure-Requests': '1',
'Connection': 'keep-alive'
}, {
# Edge 17
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'zh-CN',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/17.17134',
'Upgrade-Insecure-Requests': '1',
'Connection': 'keep-alive'
}, {
# IE 11
'Accept': 'text/html, application/xhtml+xml, image/jxr, */*',
'Accept-Encoding': 'gzip, deflate',
'Accept-Language': 'zh-CN',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko',
'Connection': 'keep-alive'
}]
def access_article(soup, header):
print('>>访问文章')
tags = soup.select('#mainBox h4.text-truncate > a')
tags = random.sample(tags, random.randrange(1, len(tags))) # 随机抽取K个不重复的元素形成新列表
random.shuffle(tags) # 打乱列表顺序
for tag in tags:
sleep(random.randint(1, 5)) # 随机挂起1~5秒
href = tag['href']
print('*', href[-25:], tag.contents[2].strip())
req = request.Request(href, headers = header)
try:
res = request.urlopen(req) # 发起请求
except Exception as ex:
print('!', ex)
return
def crawl_blog(blog):
print('>>爬取博客:', blog)
cookie_handle = request.HTTPCookieProcessor(CookieJar())
opener = request.build_opener(cookie_handle) # 启用Cookie
request.install_opener(opener)
header = random.choice(HEADER_LIST).copy() # 随机生成请求头部
page = 1
while 1:
print('>>爬取第', page, '个列表页')
url = 'https://blog.csdn.net/'+blog+'/article/list/'+str(page)
print('*', url)
req = request.Request(url, headers = header)
res = None
try:
res = request.urlopen(req) # 发起请求
except Exception as ex:
print('!', ex)
return
if res.status != 200:
print('!', res.status, '博客地址访问失败')
return
soup = BeautifulSoup( gzip.decompress(res.read()).decode() ) # 解析响应体
header['Referer'] = url # 记录博客地址到Referer,模拟正常行为
access_article(soup, header) # 访问文章
if not soup.select('#pageBox'): # 判断是否没有下一页
return
page = page + 1 # 下个列表页
# 主程序
while 1:
print('=========================')
for blog in BLOG_LIST:
crawl_blog(blog) # 爬取博客
interval = CRAWL_INTERVAL + random.randint(0, 50)
print('>>挂起', interval, '秒')
sleep(interval)