常见的反爬手段和解决思路
目标
- 了解常用的反爬手段和解决思路
1. 明确反反爬的主要思路
反反爬的主要思路就是:尽可能的去模拟浏览器,浏览器在如何操作,代码中就如何去实现。浏览器先请求了地址url1,保留了cookie在本地,之后请求地址url2,带上了之前的cookie,代码中也可以这样去实现。
很多时候,爬虫中携带的headers字段,cookie字段,url参数,post的参数很多,不清楚哪些有用,哪些没用的情况下,只能够去尝试,因为每个网站都是不相同的。当然在盲目尝试之前,可以参考别人的思路,我们自己也应该有一套尝试的流程。
2.通过headers字段来反爬
2.1 通过headers中的User-Agent字段来反爬
通过User-Agent字段反爬的话,只需要给他在请求之前添加User-Agent即可,更好的方式是使用User-Agent池来解决,我们可以考虑收集一堆User-Agent的方式,或者是随机生成User-Agent
import random
def get_ua(): first_num = random.randint(55, 62) third_num = random.randint(0, 3200) fourth_num = random.randint(0, 140) os_type = [ '(Windows NT 6.1; WOW64)', '(Windows NT 10.0; WOW64)', '(X11; Linux x86_64)', '(Macintosh; Intel Mac OS X 10_12_6)' ] chrome_version = 'Chrome/{}.0.{}.{}'.format(first_num, third_num, fourth_num) ua = ' '.join(['Mozilla/5.0', random.choice(os_type), 'AppleWebKit/537.36', '(KHTML, like Gecko)', chrome_version, 'Safari/537.36'] ) return ua
2.2 通过referer字段或者是其他字段来反爬
例如豆瓣电视剧中,通过referer字段来反爬,我们只需要添加上即可
2.3 通过cookie来反爬
-
如果目标网站不需要登录 每次请求带上前一次返回的cookie,比如requests模块的session
-
如果目标网站需要登录 准备多个账号,通过一个程序获取账号对应的cookie,组成cookie池,其他程序使用这些cookie
3.通过js来反爬
3.1 通过js实现跳转来反爬
在请求目标网站的时候,我们看到的似乎就请求了一个网站,然而实际上在成功请求目标网站之前,中间可能有通过js实现的跳转,我们肉眼不可见,这个时候可以通过点击perserve log按钮实现观察页面跳转情况
在这些请求中,如果请求数量很多,一般来讲,只有那些response中带cookie字段的请求是有用的,意味着通过这个请求,对方服务器有设置cookie到本地
3.2 通过js生成了请求参数
对应的需要分析js,观察加密的实现过程
在下一小节,学习了selenium这个问题会变得很容易
3.3 通过js实现了数据的加密
对应的需要分析js,观察加密的实现过程,学习了selenium这个问题会变得很容易
4. 通过验证码来反爬
通过打码平台或者是机器学习的方法识别验证码,其中打码平台廉价易用,更值得推荐.
5. 通过ip地址来反爬
同一个ip大量请求了对方服务器,有更大的可能性会被识别为爬虫,对应的通过购买高质量的ip的方式能够结局问题
6. 其他的反爬方式
6.1 通过自定义字体来反爬
解决思路:切换到手机版
6.2 通过css来反爬
解决思路:计算css的偏移
selenium的使用
1. 什么是selenium
Selenium是一个Web的自动化测试工具,最初是为网站自动化测试而开发的,Selenium 可以直接运行在浏览器上,它支持所有主流的浏览器(包括PhantomJS这些无界面的浏览器),可以接收指令,让浏览器自动加载页面,获取需要的数据,甚至页面截屏
2. PhantomJS的介绍
PhantomJS 是一个基于Webkit的“无界面”(headless)浏览器,它会把网站加载到内存并执行页面上的 JavaScript
3. Chromedriver的介绍
Chromedriver 也是一个能够被selenium驱动的浏览器,但是和PhantomJS的区别在于它是有界面的
4. driver的安装
最简单的安装方式是:解压后把bin目录下的可执行文件移动到环境变量下,比如/usr/bin
或者是/usr/local/bin
下面
注意:Chromedriver和电脑上的chrome版本有对应关系,建议使用最新的Chromedriver版本并且更新chrome浏览器到最新版
5. selenium的入门使用
-
知识点:
- 掌握如何发送请求,加载网页
- 掌握如何进行简单的元素定位
- 掌握如何从获取浏览器中的数据
-
加载网页: selenium通过控制浏览器,所以对应的获取的数据都是elements中的内容
from selenium import webdriver driver = webdriver.PhantomJS(“c:…/pantomjs.exe”) driver.get("http://www.baidu.com/") driver.save_screenshot("长城.png")
-
定位和操作:
driver.find_element_by_id(“kw”).send_keys(“长城”) driver.find_element_by_id("su").click()
-
查看请求信息:
driver.page_source driver.get_cookies() driver.current_url
-
退出
driver.close() #退出当前页面 driver.quit() #退出浏览器
6. selenium的定位操作
-
知识点:
- 掌握定位元素的方法
- 掌握获取元素中数据的方法
-
定位元素语法:
find_element_by_id (返回一个元素) find_elements_by_xpath (返回一个包含元素的列表) find_elements_by_link_text (根据连接文本获取元素列表) find_elements_by_partial_link_text (根据连接包含的文本获取元素列表) find_elements_by_tag_name (根据标签名获取元素列表) find_elements_by_class_name (根据类名获取元素列表)
注意:
find_element
和find_elements
的区别by_link_text
和by_partial_link_tex
的区别:全部文本和包含某个文本 -
使用:
以豆瓣首页为例:https://www.douban.com/
from selenium import webdriver driver =webdriver.Chrome() driver.get("https://www.douban.com/") ret1 = driver.find_element_by_id("anony-nav") print(ret1) # 输出为:<selenium.webdriver.remote.webelement.WebElement (session="ea6f94544ac3a56585b2638d352e97f3", element="0.5335773935305805-1")> ret2 = driver.find_elements_by_id("anony-nav") print(ret2) #输出为:[<selenium.webdriver.remote.webelement.WebElement (session="ea6f94544ac3a56585b2638d352e97f3", element="0.5335773935305805-1")>] ret3 = driver.find_elements_by_xpath("//*[@id='anony-nav']/h1/a") print(len(ret3)) #输出为:1 ret4 = driver.find_elements_by_tag_name("h1") print(len(ret4)) #输出为:1 ret5 = driver.find_elements_by_link_text("下载豆瓣 App") print(len(ret5)) #输出为:1 ret6 = driver.find_elements_by_partial_link_text("豆瓣") print(len(ret6)) #输出为:28 driver.close()
- 获取数据语法
- find_element仅仅能够获取元素,不能顾直接获取其中的数据,
find_element_by_xapth
也是这样 - 获取文本:
element.text
- 获取属性值:
element.get_attribute("href")
- find_element仅仅能够获取元素,不能顾直接获取其中的数据,
- 使用示例:
from selenium import webdriver
driver =webdriver.Chrome() driver.get("https://www.douban.com/") ret4 = driver.find_elements_by_tag_name("h1") print(ret4[0].text) #输出:豆瓣 ret5 = driver.find_elements_by_link_text("下载豆瓣 App") print(ret5[0].get_attribute("href")) #输出:https://www.douban.com/doubanapp/app?channel=nimingye driver.close()
7. selenium 处理cookie
通过driver.get_cookies()
能够获取所有的cookie
# 把cookie转化为字典
{cookie[‘name’]: cookie[‘value’] for cookie in driver.get_cookies()} #删除一条cookie driver.delete_cookie("CookieName") # 删除所有的cookie driver.delete_all_cookies()
8. 页面等待
-
为什么需要等待
如果网站采用了动态html技术,那么页面上的部分元素出现时间便不能确定,这个时候就可以设置一个等待时间,强制要求在时间内出现,否则报错
-
页面等待的方法
time.sleep(10)
9.动手
爬取斗鱼直播平台的所有房间信息:https://www.douyu.com/directory/all
# coding=utf-8
from selenium import webdriver
import time
class DouYu:
def __init__(self):
self.start_url = "https://www.douyu.com/directory/all"
self.driver = webdriver.Chrome()
def get_content_list(self): #提取数据
li_list = self.driver.find_elements_by_xpath("//ul[@id='live-list-contentbox']/li")
content_list = []
for li in li_list:
item = {}
item["title"] = li.find_element_by_xpath("./a").get_attribute("title")
item["anchor"] = li.find_element_by_xpath(".//span[@class='dy-name ellipsis fl']").text
item["watch_num"] = li.find_element_by_xpath(".//span[@class='dy-num fr']").text
print(item)
content_list.append(item)
#提取下一页的元素
next_url = self.driver.find_elements_by_xpath("//a[@class='shark-pager-next']")
next_url = next_url[0] if len(next_url)>0 else None
return content_list,next_url
def save_content_list(self,content_lsit):#保存
pass
def run(self): #实现主要逻辑
#1. start_url
#2. 发送请求,获取响应
self.driver.get(self.start_url)
#3. 提取数据
content_list,next_url = self.get_content_list()
#4.保存
self.save_content_list(content_list)
# 5. 下一页数据的提取
while next_url is not None:
next_url.click() #页面没有完全加载完,会报错
time.sleep(3)
content_list,next_url = self.get_content_list()
self.save_content_list(content_list)
if __name__ == '__main__':
douyu = DouYu()
douyu.run()
10. 使用selenium切换frame
frame是html中常用的一种技术,即一个页面中嵌套了另一个网页,selenium默认是访问不了frame中的内容的,对应的解决思路是 driver.switch_to.frame()
动手:模拟登陆qq邮箱
在使用selenium登录qq邮箱的过程中,我们会发现,无法在邮箱的登录input标签中输入内容,通过观察源码可以发现,form表单在一个frame中,所以需要切换到frame中
# coding=utf-8
from selenium import webdriver
import time
driver = webdriver.Chrome()
driver.get("https://mail.qq.com/")
driver.switch_to.frame("login_frame")
driver.find_element_by_id("u").send_keys("hello")
time.sleep(5)
driver.quit()
11. selenium的优缺点
- selenium能够执行页面上的js,对于js渲染的数据和模拟登陆处理起来非常容易
- selenium由于在获取页面的过程中会发送很多请求,所以效率非常低,所以在很多时候需要酌情使用
网易云音乐的爬虫demo
# coding=utf-8
import requests
from lxml import etree
import re
from selenium import webdriver
from copy import deepcopy
class Music163:
def __init__(self):
self.start_url = "http://music.163.com/discover/playlist"
self.headers = {"User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36"}
def parse_url(self,url):
print(url)
resp = requests.get(url,headers=self.headers)
return resp.content.decode()
def get_category_list(self):#获取大分类和小分类
resp = self.parse_url(self.start_url)
html = etree.HTML(resp)
dl_list = html.xpath("//div[@class='bd']/dl")
category_list = []
for dl in dl_list:
b_cate = dl.xpath("./dt/text()")[0] if len(dl.xpath("./dt/text()"))>0 else None
a_list = dl.xpath("./dd/a")
for a in a_list:
item = {}
item["b_cate"]= b_cate
item["s_cate"] = a.xpath("./text()")[0] if len(a.xpath("./text()"))>0 else None
item["s_href"] = "http://music.163.com"+a.xpath("./@href")[0] if len(a.xpath("./@href"))>0 else None
category_list.append(item)
return category_list
def get_playlist_list(self,item,total_playlist_list):#获取小分类中的playlist列表
playlist_list = []
if item["s_href"] is not None:
scate_resp = self.parse_url(item["s_href"])
scate_html = etree.HTML(scate_resp)
li_list = scate_html.xpath("//ul[@id='m-pl-container']/li")
for li in li_list:
item["playlist_title"] = li.xpath("./p[@class='dec']/a/@title")[0] if len(li.xpath("./p[@class='dec']/a/@title"))>0 else None
print(item["playlist_title"])
item["playlist_href"] = "http://music.163.com"+li.xpath("./p[@class='dec']/a/@href")[0] if len(li.xpath("./p[@class='dec']/a/@href"))>0 else None
item["author_name"] = li.xpath("./p[last()]/a/@title")[0] if len(li.xpath("./p[last()]/a/@title"))>0 else None
item["author_href"] = "http://music.163.com"+li.xpath("./p[last()]/a/@href")[0] if len(li.xpath("./p[last()]/a/@href"))>0 else None
playlist_list.append(deepcopy(item))
total_playlist_list.extend(playlist_list)
next_url = scate_html.xpath("//a[text()='下一页']/@href")[0] if len(scate_html.xpath("//a[text()='下一页']/@href"))>0 else None
if next_url is not None and next_url!='javascript:void(0)':
item["s_href"] = "http://music.163.com"+next_url
#递归,调用自己,获取下一页的播放列表,直到下一页没有的时候不再递归
return self.get_playlist_list(item,total_playlist_list)
return total_playlist_list
def get_playlist_info(self,playlist): #获取单个播放别表的信息
if playlist["playlist_href"] is not None:
playlist_resp = self.parse_url(playlist["playlist_href"])
playlist["covers"] = re.findall("\"images\": .*?\[\"(.*?)\"\],",playlist_resp)
playlist["covers"] = playlist["covers"][0] if len(playlist["covers"])>0 else None
playlist["create_time"] = re.findall("\"pubDate\": \"(.*?)\"",playlist_resp)
playlist["create_time"] = playlist["create_time"][0] if len(playlist["create_time"])>0 else None
playlist_html = etree.HTML(playlist_resp)
playlist["favorited_times"] = playlist_html.xpath("//a[@data-res-action='fav']/@data-count")[0] if len(playlist_html.xpath("//a[@data-res-action='fav']/@data-count"))>0 else None
playlist["shared_times"] = playlist_html.xpath("//a[@data-res-action='share']/@data-count")[0] if len(playlist_html.xpath("//a[@data-res-action='share']/@data-count"))>0 else None
playlist["desc"] = playlist_html.xpath("//p[@id='album-desc-dot']/text()")
playlist["played_times"] = playlist_html.xpath("//strong[@id='play-count']/text()")[0] if len(playlist_html.xpath("//strong[@id='play-count']/text()"))>0 else None
playlist["tracks"] = self.get_playlist_tracks(playlist["playlist_href"])
return playlist
def get_playlist_tracks(self,href): #获取每个歌单的歌曲信息
driver = webdriver.Chrome()
driver.get(href)
driver.switch_to.frame("g_iframe")
tr_list = driver.find_elements_by_xpath("//tbody/tr")
playlist_tracks = []
for tr in tr_list:
track = {}
track["name"] = tr.find_element_by_xpath("./td[2]//b").get_attribute("title")
track["duration"] = tr.find_element_by_xpath("./td[3]/span").text
track["singer"] = tr.find_element_by_xpath("./td[4]/div").get_attribute("title")
track["album_name"] = tr.find_element_by_xpath("./td[5]//a").get_attribute("title")
playlist_tracks.append(track)
driver.quit()
return playlist_tracks
def run(self):
categroy_list = self.get_category_list() #获取分类
for cate in categroy_list:
total_playlist_list = self.get_playlist_list(cate,[]) #获取每个分类下的所有播放列表
print("-"*100)
print(total_playlist_list)
print("-"*100)
for playlist in total_playlist_list:
print(playlist,"*"*100)
playlist = self.get_playlist_info(playlist) #获取每个播放列表下的所有歌曲信息
print(playlist)
if __name__ == '__main__':
music_163 = Music163()
music_163.run()
12.selenium的扩展知识
import time
# 基本用法
# from selenium import webdriver
# driver = webdriver.Chrome()
# driver.get("http:\\www.baidu.com")
# driver.save_screenshot('./baidu.png')
# time.sleep(2)
# driver.quit()
# 无界面使用方法
# from selenium import webdriver
# from selenium.webdriver.chrome.options import Options
#
# chrome_options = Options()
# chrome_options.add_argument('--headless')
# driver = webdriver.Chrome(chrome_options=chrome_options)
# driver.get("http:\\www.baidu.com")
# driver.save_screenshot('./baidu.png')
# time.sleep(1)
# driver.quit()
# 跳转窗口用法
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
# chrome_options = Options()
# chrome_options.add_argument('--headless')
# driver = webdriver.Chrome(chrome_options=chrome_options)
# driver = webdriver.Chrome()
# 获取当前窗口句柄 句柄一般指唯一标识符
# now_handle = driver.current_window_handle
# 获取所有窗口句柄
# all_handles = driver.window_handles
# 切换回原窗口
# driver.switch_to_window(now_handle)
# time.sleep(2)
# 设置代理
# from selenium import webdriver
# chromeOptions = webdriver.ChromeOptions()
# 一定要注意,=两边不能有空格,不能是这样--proxy-server = http://202.20.16.82:10152
# chromeOptions.add_argument("--proxy-server=http://202.20.16.82:10152")
# browser = webdriver.Chrome(chrome_options = chromeOptions)
# 设置请求头
# from selenium import webdriver
# options = webdriver.ChromeOptions()
# 设置中文
# options.add_argument('lang=zh_CN.UTF-8')
# 更换头部
# options.add_argument('user-agent="Mozilla/5.0 (iPod; U; CPU iPhone OS 2_1 like Mac OS X; ja-jp) AppleWebKit/525.18.1 (KHTML, like Gecko) Version/3.1.1 Mobile/5F137 Safari/525.20"')
# browser = webdriver.Chrome(chrome_options=options)
# url = "https://httpbin.org/get?show_env=1"
# browser.get(url)
# browser.quit()
# 设置不加载图片
from selenium import webdriver
# options = webdriver.ChromeOptions()
# prefs = {
# 'profile.default_content_setting_values': {
# 'images': 2
# }
# }
# options.add_experimental_option('prefs', prefs)
# browser = webdriver.Chrome(chrome_options=options)
# browser = webdriver.Chrome()
# url = "http://image.baidu.com/"
# browser.get(url)
# input("是否有图")
# browser.quit()
爬取开始吧demo
from selenium import webdriver
options = webdriver.ChromeOptions()
prefs = {
'profile.default_content_setting_values': {
'images': 2 # 2表示禁用图片加载,提高速度
}
}
options.add_experimental_option('prefs', prefs)
options.add_argument('--headless')
driver = webdriver.Chrome(chrome_options=options)
driver.get("https://www.kaishiba.com/project/more")
driver.save_screenshot('./startBa.png')
while True:
js = "window.scrollTo(0,document.body.scrollHeight)" # 模拟浏览器的下拉动作
driver.execute_script(js)
# time.sleep(random.randint(2,5))
l_list = driver.find_elements_by_xpath("//li[@class='programCard']")
print('获取%d条数据' % len(l_list))
if len(l_list)==1800:
with open('./start.html','w',encoding='utf-8') as f:
f.write(driver.page_source)
break
driver.quit()
打码平台的使用
目标
- 了解常见的打码平台
- 掌握如果通过打码平台处理验证码
1. 为什么需要了解打码平台的使用
现在很多网站都会使用验证码来进行反爬,所以为了能够更好的获取数据,需要了解如何使用打码平台爬虫中的验证码
2. 常见的打码平台
-
能够解决通用的验证码识别
-
极验验证码智能识别辅助:http://jiyandoc.c2567.com/
能够解决复杂验证码的识别
3. 云打码的使用
下面代码是云打码平台提供,做了个简单修改,只用传入response.content 即可识别图片
import requests
import json
import time
class YDMHttp:
apiurl = 'http://api.yundama.com/api.php'
username = ''
password = ''
appid = ''
appkey = ''
def __init__(self, username, password, appid, appkey):
self.username = username
self.password = password
self.appid = str(appid)
self.appkey = appkey
def request(self, fields, files=[]):
response = self.post_url(self.apiurl, fields, files)
response = json.loads(response)
return response
def balance(self):
data = {'method': 'balance', 'username': self.username, 'password': self.password, 'appid': self.appid,
'appkey': self.appkey}
response = self.request(data)
if (response):
if (response['ret'] and response['ret'] < 0):
return response['ret']
else:
return response['balance']
else:
return -9001
def login(self):
data = {'method': 'login', 'username': self.username, 'password': self.password, 'appid': self.appid,
'appkey': self.appkey}
response = self.request(data)
if (response):
if (response['ret'] and response['ret'] < 0):
return response['ret']
else:
return response['uid']
else:
return -9001
def upload(self, filename, codetype, timeout):
data = {'method': 'upload', 'username': self.username, 'password': self.password, 'appid': self.appid,
'appkey': self.appkey, 'codetype': str(codetype), 'timeout': str(timeout)}
file = {'file': filename}
response = self.request(data, file)
if (response):
if (response['ret'] and response['ret'] < 0):
return response['ret']
else:
return response['cid']
else:
return -9001
def result(self, cid):
data = {'method': 'result', 'username': self.username, 'password': self.password, 'appid': self.appid,
'appkey': self.appkey, 'cid': str(cid)}
response = self.request(data)
return response and response['text'] or ''
def decode(self, filename, codetype, timeout):
cid = self.upload(filename, codetype, timeout)
if (cid > 0):
for i in range(0, timeout):
result = self.result(cid)
if (result != ''):
return cid, result
else:
time.sleep(1)
return -3003, ''
else:
return cid, ''
def post_url(self, url, fields, files=[]):
# for key in files:
# files[key] = open(files[key], 'rb');
res = requests.post(url, files=files, data=fields)
return res.text
username = 'whoarewe' # 用户名
password = '***' # 密码
appid = 4283 # appid
appkey = '02074c64f0d0bb9efb2df455537b01c3' # appkey
filename = 'getimage.jpg' # 文件位置
codetype = 1004 # 验证码类型
# 超时
timeout = 60
def indetify(response_content):
if (username == 'username'):
print('请设置好相关参数再测试')
else:
# 初始化
yundama = YDMHttp(username, password, appid, appkey)
# 登陆云打码
uid = yundama.login();
print('uid: %s' % uid)
# 查询余额
balance = yundama.balance();
print('balance: %s' % balance)
# 开始识别,图片路径,验证码类型ID,超时时间(秒),识别结果
cid, result = yundama.decode(response_content, codetype, timeout)
print('cid: %s, result: %s' % (cid, result))
return result
def indetify_by_filepath(file_path):
if (username == 'username'):
print('请设置好相关参数再测试')
else:
# 初始化
yundama = YDMHttp(username, password, appid, appkey)
# 登陆云打码
uid = yundama.login();
print('uid: %s' % uid)
# 查询余额
balance = yundama.balance();
print('balance: %s' % balance)
# 开始识别,图片路径,验证码类型ID,超时时间(秒),识别结果
cid, result = yundama.decode(file_path, codetype, timeout)
print('cid: %s, result: %s' % (cid, result))
return result
if __name__ == '__main__':
pass
4.常见的验证码的种类
4.1 url地址不变,验证码不变
这是验证码里面非常简单的一种类型,对应的只需要获取验证码的地址,然后请求,通过打码平台识别即可
4.2 url地址不变,验证码变化
这种验证码的类型是更加常见的一种类型,对于这种验证码,大家需要思考:
在登录的过程中,假设我输入的验证码是对的,对方服务器是如何判断当前我输入的验证码是显示在我屏幕上的验证码,而不是其他的验证码呢?
在获取网页的时候,请求验证码,以及提交验证码的时候,对方服务器肯定通过了某种手段验证我之前获取的验证码和最后提交的验证码是同一个验证码,那这个手段是什么手段呢?
很明显,就是通过cookie来实现的,所以对应的,在请求页面,请求验证码,提交验证码的到时候需要保证cookie的一致性,对此可以使用requests.session来解决
from selenium import webdriver
options = webdriver.ChromeOptions()
prefs = {
'profile.default_content_setting_values': {
'images': 2 # 2表示禁用图片加载,提高速度
}
}
options.add_experimental_option('prefs', prefs)
options.add_argument('--headless')
driver = webdriver.Chrome(chrome_options=options)
driver.get("https://www.kaishiba.com/project/more")
driver.save_screenshot('./startBa.png')
while True:
js = "window.scrollTo(0,document.body.scrollHeight)" # 模拟浏览器的下拉动作
driver.execute_script(js)
# time.sleep(random.randint(2,5))
l_list = driver.find_elements_by_xpath("//li[@class='programCard']")
print('获取%d条数据' % len(l_list))
if len(l_list)==1800:
with open('./start.html','w',encoding='utf-8') as f:
f.write(driver.page_source)
break
driver.quit()