1.爬虫的基本概念
爬虫:自动获取网站数据的程序,关键是批量的获取
反爬虫:使用技术手段防止爬虫程序的方法
误伤:反爬技术将普通用户识别为爬虫,如果误伤过高,效果再好也不能用
成本:反爬虫需要的人力和机器成本
拦截:成功拦截爬虫,一般拦截率越高,误伤率越高
2.反爬虫的目的
初级爬虫:简单粗暴,不管服务器压力,容易弄挂网站
数据保护:具有知识产权的数据
失控的爬虫:由于某些情况下,忘记或者无法关闭的爬虫
商业竞争对手:防止被对手爬走了数据
3.爬虫和反爬虫的经典应对场景(重点)
4.随机更换User-Agent
通过之前的执行流程图可以看到,Requests和Response之间有很多的Middleware,这个UserAgentMiddleware就是默认用来处理User-Agent的。
从源码可以看到,如果不在settings文件中定义USER_AGENT,那么就会使用默认的"Scrapy"
4.1 全局User-Agent
如果要定义全局默认的User-Agent,可以在settings.py文件中去定义
# 定义全局的User-Agent
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36"
4.2 局部User-Agent
局部user-agent是在我们自己写的spider代码里面定义的,可以通过custom_settings来定义
custom_settings = {
# 是否开启自定义cookie,如果为False则使用settings中的cookie
"COOKIES_ENABLED": True,
# 下载延迟时间,值越大请求越慢
"DOWNLOAD_DELAY": 10,
# 是否开启随机延迟时间
"RANDOMIZE_DOWNLOAD_DELAY": True,
# 并发请求数
"CONCURRENT_REQUESTS": 1,
# 浏览器的user-agent(注意这里的名字是USER_AGENT)
"USER_AGENT": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.4951.54 Safari/537.36",
}
4.3 fake-useragent(重点)
官方地址:https://github.com/hellysmile/fake-useragent
默认维护的User-Agent的链接:https://fake-useragent.herokuapp.com/browsers/0.1.11(后面是最新版本号)
4.3.1 安装模块
pip install fake-useragent
4.3.2 Middleware编写
from fake_useragent import UserAgent
# 随机更换User-Agent
class RandomUserAgentMiddleware(object):
def __init__(self, crawler):
super(RandomUserAgentMiddleware, self).__init__()
self.ua = UserAgent()
@classmethod
def from_crawler(cls, crawler):
return cls(crawler)
def process_request(self, request, spider):
request.headers.setdefault(b'User-Agent', self.ua.random)
4.3.3 settings配置
DOWNLOADER_MIDDLEWARES = {
# 'article_spider.middlewares.ArticleSpiderDownloaderMiddleware': 543,
# 注释掉默认的useragent的中间件
# 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
# 自定义useragent的中间件
'article_spider.middlewares.RandomUserAgentMiddleware': 1
}
4.3.4 如何自定义生成的策略?
fake-useragent默认支持很多种的user-agent的生成方式,有如下类型:ua.ie、ua.opera、ua.chrome等
我们有的时候就想只生产chrome的user-agent,那如何动态切换配置了?
动态切换user-agent的生成策略
通过self.ua_type = crawler.settings.get("RANDOM_UA_TYPE", "random"),可以读取配置文件的数据
通过get_ua()函数可以动态调用对应的方法,相当于:self.ua.self.ua_type,但是python语法是不支持这样调用的。所以我们通过get_ua()这个内部函数来实现
from fake_useragent import UserAgent
# 随机更换User-Agent
class RandomUserAgentMiddleware(object):
def __init__(self, crawler):
super(RandomUserAgentMiddleware, self).__init__()
self.ua = UserAgent()
self.ua_type = crawler.settings.get("RANDOM_UA_TYPE", "random")
@classmethod
def from_crawler(cls, crawler):
return cls(crawler)
def process_request(self, request, spider):
# 动态获得配置的策略
def get_ua():
return getattr(self.ua, self.ua_type)
request.headers.setdefault(b'User-Agent', get_ua())
RANDOM_UA_TYPE这个是自定义配置,默认为random
以后如果想改成chrome或其他的,只需要更改settings中的这个配置即可
DOWNLOADER_MIDDLEWARES = {
# 'article_spider.middlewares.ArticleSpiderDownloaderMiddleware': 543,
# 注释掉默认的useragent的中间件
# 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
# 自定义useragent的中间件
'article_spider.middlewares.RandomUserAgentMiddleware': 1
}
# 随机user-agent的策略
RANDOM_UA_TYPE = "random"
5.IP代理池
有些网站会限制同一个IP频繁爬取网站,这个时候就需要用到IP代理池了。
IP代理推荐:https://zhuanlan.zhihu.com/p/33576641
5.1 爬取代理IP
5.2 实现随机取代理IP
5.2.1 数据库设计
CREATE TABLE `proxy_ip` (
`ip` varchar(255) NOT NULL,
`port` varchar(255) NOT NULL,
`speed` float DEFAULT NULL,
`proxy_type` varchar(5) DEFAULT NULL,
PRIMARY KEY (`ip`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
5.2.2 代码
# mysql高效的取随机数据
SELECT *
FROM `table` AS t1 JOIN (SELECT ROUND(RAND() * ((SELECT MAX(id) FROM `table`)-(SELECT MIN(id) FROM `table`))+(SELECT MIN(id) FROM `table`)) AS id) AS t2
WHERE t1.id >= t2.id
ORDER BY t1.id LIMIT 1;
class ProxyIP(object):
# 随机提取IP
def get_random_ip(self):
random_ip_sql = """
select ip, port from proxy_ip
order by RAND()
limit 1
"""
cursor.execute(random_ip_sql)
for ip_info in cursor.fetchall():
ip = ip_info[0]
port = ip_info[1]
# 判断ip是否可用
if self.judge_ip(ip, port):
return "http://{0}:{1}".format(ip, port)
else:
# 如果不可用,继续查找可用IP
self.get_random_ip()
# 判断ip是否可用
def judge_ip(self, ip, port):
http_url = "http://www.baidu.com"
proxy_url = "http://{0}:{1}".format(ip, port)
proxy_dict = {
"http": proxy_url,
# "https": proxy_url,
}
try:
resp = requests.get(http_url, proxies=proxy_dict)
code = resp.status_code
if 200 <= code < 300:
print("effective ip")
return True
else:
# 如果ip不可用,删除ip
print("invalid ip and port")
self.delete_ip(ip)
return False
except Exception as e:
print("invalid ip and port")
print(e)
self.delete_ip(ip)
return False
# 删除IP
def delete_ip(self, ip):
delete_ip_sql = """
delete from proxy_ip where ip = '{0}'
""".format(ip)
cursor.execute(delete_ip_sql)
conn.commit()
return True
if __name__ == '__main__':
proxy_ip = ProxyIP()
print(proxy_ip.get_random_ip())
5.2.3 Middleware中间件
from article_spider.tools.proxy_ip import ProxyIP
# 随机获取代理IP
# github上有scrapy-proxies,提供了很多配置功能,可以自己改造一下
# github上有scrapy-crawlera,这个是收费的
# python使用Tor(洋葱浏览器)爬取网站
class RandomIPMiddleware(object):
def process_request(self, request, spider):
proxy_ip = ProxyIP()
request.meta["proxy"] = proxy_ip.get_random_ip()
5.2.4 settings配置
DOWNLOADER_MIDDLEWARES = {
# 自定义useragent的中间件
'article_spider.middlewares.RandomUserAgentMiddleware': 1,
# 随机IP代理
'article_spider.middlewares.RandomIPMiddleware': 2
}
5.3 总结
我们在爬取网站的时候,总体还是要采用后面讲的限速的策略,不要爬的太快了,一定要珍惜自己的IP!
如果你想爬的更快的话,可以使用IP代理池进行爬取
5.4 其他好用的IP代理的实现
上面的代理IP都是爬取的免费的,免费的一般都不稳定,可以考虑一些付费的IP代理
5.4.1 scrapy-proxies
这个都是在settings文件中进行配置的,不支持数据库操作,所以可以自己对源码改一下。
官方地址:https://github.com/aivarsk/scrapy-proxies
5.4.2 scrapy-crawlera(收费)
这个是代理IP商提供的开源代码,但是使用他们的代理IP是收费的
官方地址:https://github.com/scrapy-plugins/scrapy-zyte-smartproxy
官方文档:https://scrapy-zyte-smartproxy.readthedocs.io/en/latest/
5.4.2 Tor(洋葱浏览器)爬取网站
洋葱浏览器可以使用代理隐藏自己的真实IP地址,常用于黑客使用,我们的python也可以操作Tor来爬取网站
文章:https://copyfuture.com/blogs-details/2020051818370565011ksvjjtfy8inp9
6.验证码识别
6.1 编码实现(tesseract-ocr)
这个识别准确度比较低,不太推荐使用
还可以使用之前我们写的opencv识别工具类:https://www.yuque.com/suifengbiji/kzz2hu/lufd1y#MKjYY
6.2 在线打码(推荐)
超级鹰:超级鹰验证码识别-专业的验证码云端识别服务,让验证码识别更快速、更准确、更强大
6.2.1 超级鹰打码平台代码
1)工具类
class Chaojiying_Client(object):
def __init__(self, username, password, soft_id):
self.username = username
password = password.encode('utf8')
self.password = md5(password).hexdigest()
self.soft_id = soft_id
self.base_params = {
'user': self.username,
'pass2': self.password,
'softid': self.soft_id,
}
self.headers = {
'Connection': 'Keep-Alive',
'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)',
}
def PostPic(self, im, codetype):
"""
im: 图片字节
codetype: 题目类型 参考 http://www.chaojiying.com/price.html
"""
params = {
'codetype': codetype,
}
params.update(self.base_params)
files = {'userfile': ('ccc.jpg', im)}
r = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=params, files=files,
headers=self.headers)
return r.json()
def ReportError(self, im_id):
"""
im_id:报错题目的图片ID
"""
params = {
'id': im_id,
}
params.update(self.base_params)
r = requests.post('http://upload.chaojiying.net/Upload/ReportError.php', data=params, headers=self.headers)
return r.json()
if __name__ == '__main__':
# 用户中心>>软件ID 生成一个替换 96001
chaojiying = Chaojiying_Client('13279385584', 'ky6856', '930856')
# 本地图片文件路径 来替换 a.jpg 有时WIN系统须要//
im = open('a.jpg', 'rb').read()
# 1902 验证码类型 官方网站>>价格体系 3.4+版 print 后要加()
print(chaojiying.PostPic(im, 1902))
2)识别超级鹰自己的登录验证码
from selenium.webdriver import Chrome
from selenium.webdriver.common.by import By
import time
from chaojiying import Chaojiying_Client
web = Chrome()
web.get("http://www.chaojiying.com/user/login/")
# 截取验证码图片,自动获得图片的二进制
img = web.find_element(By.XPATH, "/html/body/div[3]/div/div[3]/div[1]/form/div/img").screenshot_as_png
# 识别验证码
chaojiying = Chaojiying_Client('13279385584', 'ky6856', '930856')
dic = chaojiying.PostPic(img, 1902)
verify_code = dic.get("pic_str")
# 填写用户名
web.find_element(By.XPATH, "/html/body/div[3]/div/div[3]/div[1]/form/p[1]/input").send_keys("13279385584")
# 填写密码
web.find_element(By.XPATH, "/html/body/div[3]/div/div[3]/div[1]/form/p[2]/input").send_keys("ky6856")
# 填写验证码
web.find_element(By.XPATH, "/html/body/div[3]/div/div[3]/div[1]/form/p[3]/input").send_keys(verify_code)
time.sleep(5)
# 点击登陆
web.find_element(By.XPATH, "/html/body/div[3]/div/div[3]/div[1]/form/p[4]/input").click()
6.3 人工代码
后面是真人在给你识别,价格最贵,但是识别率肯定是最高的
7.禁用Cookie
将Cookie禁掉,就不会在Requests中携带cookie了,那网站就无法跟踪,适用于不需要登录的网站。
我们可以在scrapy中的settings.py文件中做全局配置
# Disable cookies (enabled by default)
COOKIES_ENABLED = False
也可以在我们具体的spider代码中做自定义配置custom_settings
class ZhihuSpider(scrapy.Spider):
name = 'zhihu'
allowed_domains = ['www.zhihu.com']
start_urls = ['http://www.zhihu.com/']
# 禁用Cookie
custom_settings = {
"COOKIES_ENABLED": False
}
}
8.限制爬取速度
中文文档:AutoThrottle 扩展 — Scrapy 2.5.0 文档
官方文档:AutoThrottle extension — Scrapy 2.11.2 documentation
主要是通过这个AutoThrottle扩展来实现限制速度,可以在settings文件中配置
也可以在spider代码中自定义配置custom_settings
8.1 介绍
# Enable and configure the AutoThrottle extension (disabled by default)
# See https://docs.scrapy.org/en/latest/topics/autothrottle.html
AUTOTHROTTLE_ENABLED = True
# The initial download delay
AUTOTHROTTLE_START_DELAY = 5
# The maximum download delay to be set in case of high latencies
AUTOTHROTTLE_MAX_DELAY = 60
# The average number of requests Scrapy should be sending in parallel to
# each remote server
AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
# Enable showing throttling stats for every response received:
AUTOTHROTTLE_DEBUG = False
# Configure a delay for requests for the same website (default: 0)
# See https://docs.scrapy.org/en/latest/topics/settings.html#download-delay
# 下载延迟时间,每隔3秒下载一个网页
DOWNLOAD_DELAY = 3
# The download delay setting will honor only one of:
CONCURRENT_REQUESTS_PER_DOMAIN = 16
CONCURRENT_REQUESTS_PER_IP = 16
8.2 代码
cookie_list = "111111"
custom_settings = {
# 是否开启自定义cookie,如果为False则使用settings中的cookie
"COOKIES_ENABLED": True,
# 下载延迟时间,值越大请求越慢
"DOWNLOAD_DELAY": 10,
# 是否开启随机延迟时间
"RANDOMIZE_DOWNLOAD_DELAY": True,
# 并发请求数
"CONCURRENT_REQUESTS": 1,
# 浏览器的user-agent(注意这里的名字是USER_AGENT)
"USER_AGENT": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.4951.54 Safari/537.36",
# 默认的请求头
'DEFAULT_REQUEST_HEADERS': {
'Accept': 'application/json, text/javascript, */*; q=0.01',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'zh-CN,zh;q=0.8',
'Connection': 'keep-alive',
'Cookie': cookie_list,
'Host': 'www.lagou.com',
'Origin': 'https://www.lagou.com',
'Referer': 'https://www.lagou.com/',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36',
}
}
9. 获取更多干货及副业软件
我是@王哪跑,关注我的工众号,持续分享python干货,各类副业技巧及软件!