爬取今日头条 街拍数据---反爬策略滑动验证码
爬取的主页:https://www.toutiao.com/search/?keyword=%E8%A1%97%E6%8B%8D
今日头条是一个js动态加载的网站
我一开始用的requests库通过接口爬取,但是发现其url请求有一个timestamp请求,一个时间戳的请求,百度了一下,发现这应该是今日头条新的反爬策略(“萌新猜测!!”),,无奈,,从未遇到这种问题,,没解决掉。然后开始尝试selenium库自动化爬取
通过selenium库进行爬取,代码如下
结果不如人意,出现了验证码,这应该就是今日头条的反爬策略,只有把这个验证码破解了,才能得到想要的数据
这也是我第一次接触到反爬验证码,在一波百度学习之后,思路如下:
由于这个验证码是自动跳出的,所以我们直接就能获取
步骤1 :没有缺口的图片--未操作的验证码
步骤2 :获取带缺口的图片
步骤3 :对比2张图片的不同,得到不一样的像素点的x值,即要移动的距离。
步骤4 :模拟人的行为(先匀加速拖动再匀减速拖动,)把需要拖动的距离分为一段段的轨迹
步骤5 : 实施拖动的过程,完成验证
步骤7: 获取数据
from selenium.webdriver.common.byimport By
from PILimport Image
from ioimport BytesIO
from selenium.webdriver.common.action_chainsimport ActionChains
import time
import re
import json
from bs4import BeautifulSoup
def get_snap(driver):#对整个网页截图,保存成图片,然后用PIL.Image拿到图片对象
'''
对整个网页截图,保存成图片,然后用PIL.Image拿到图片对象
:return: 图片对象
'''
driver.get_screenshot_as_file('snap.png')
page_snap_obj=Image.open('snap.png')
return page_snap_obj
def get_image(wait,driver):#从网页的网站截图中,截取验证码图片,图片的获取
'''
从网页的网站截图中,截取验证码图片
:return: 验证码图片
'''
img = wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'validate-main')))
time.sleep(2)# 保证图片刷新出来
print(img)
localtion = img.location
size = img.size
top = localtion['y']
bottom = localtion['y'] + size['height']
left = localtion['x']
right = localtion['x'] + size['width']
page_snap_obj = get_snap(driver)
crop_imag_obj = page_snap_obj.crop((left, top, right, bottom))
return crop_imag_obj
def get_distance(image1, image2):
'''
拿到滑动验证码需要移动的距离
:param image1:没有缺口的图片对象
:param image2:带缺口的图片对象
:return:需要移动的距离
'''
# 拿到滑动验证码需要移动的距离
# :param
# image1: 没有缺口的图片对象
# :param
# image2: 带缺口的图片对象
# :return:需要移动的距离
start =57
threhold =60
for iin range(start, image1.size[0]):
for jin range(image1.size[1]):
rgb1 = image1.load()[i, j]
rgb2 = image2.load()[i, j]
res1 =abs(rgb1[0] - rgb2[0])
res2 =abs(rgb1[1] - rgb2[1])
res3 =abs(rgb1[2] - rgb2[2])
# print(res1,res2,res3)
if not (res1 < threholdand res2 < threholdand res3 < threhold):
return i -7
return i -7
def get_tracks(distance):
'''
拿到移动轨迹,模仿人的滑动行为,先匀加速后匀减速
匀变速运动基本公式:
①v=v0+at
②s=v0t+½at²
③v²-v0²=2as
:paramdistance: 需要移动的距离
:return: 存放每0.3秒移动的距离
'''
# 初速度
v =0
# 单位时间为0.2s来统计轨迹,轨迹即0.2内的位移
t =0.3
# 位移/轨迹列表,列表内的一个元素代表0.2s的位移
tracks = []
# 当前的位移
current =0
# 到达mid值开始减速
mid = distance *4 /5
while current < distance:
if current < mid:
# 加速度越小,单位时间的位移越小,模拟的轨迹就越多越详细
a =2
else:
a = -3
# 初速度
v0 = v
# 0.2秒时间内的位移
s = v0 * t +0.5 * a * (t **2)
# 当前的位置
current += s
# 添加到轨迹列表
tracks.append(round(s))
# 速度已经达到v,该速度作为下次的初速度
v = v0 + a * t
return tracks
def main():
driver = webdriver.Chrome()
driver.get('https://www.toutiao.com/search/?keyword=%E8%A1%97%E6%8B%8D')
wait = WebDriverWait(driver, 20)
# 步骤二:拿到没有缺口的图片
image1 = get_image(wait,driver)
# 步骤三:点击拖动按钮,弹出有缺口的图片
button = wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'validate-drag-button')))
button.click()
# 步骤四:拿到有缺口的图片
image2 = get_image(wait,driver)
print(image1,image1.size)
print(image2,image2.size)
# 步骤五:对比两张图片的所有RBG像素点,得到不一样像素点的x值,即要移动的距离
distance = get_distance(image1, image2)
print(distance)
# 步骤六:模拟人的行为习惯(先匀加速拖动后匀减速拖动),把需要拖动的总距离分成一段一段小的轨迹
tracks = get_tracks(distance)
print(tracks)
print(image1.size)
print(distance, sum(tracks))
# 步骤七:按照轨迹拖动,完全验证
button = driver.find_elements_by_class_name('ovalidate-drag-button')
ActionChains(driver).click_and_hold(button).perform()
for trackin tracks:
ActionChains(driver).move_by_offset(xoffset=track, yoffset=0).perform()
else:
ActionChains(driver).move_by_offset(xoffset=3, yoffset=0).perform()# 先移过一点
ActionChains(driver).move_by_offset(xoffset=-3, yoffset=0).perform()# 再退回来,是不是更像人了
time.sleep(0.5)# 0.5秒后释放鼠标
ActionChains(driver).release().perform()
shixian(driver)
这就是验证的代码(说实话,我也不是特别理解算法),再执行shixian()函数,进行爬取Json的动态网页
def shixian(driver):
for jin range(0,1000,20): #offset每20 换一页,所以这边要设置了从offset=0 为第一页开始爬取,步数为20,到1000停止爬取,每一页数据,不过这边会出错,因为还没到1000就没有数据可以爬了。(其实只有7页,也就是0ffset=140!)
url ="https://www.toutiao.com/api/search/content/?aid=24&app_name=web_search&offset={}&format=json&keyword=%E8%A1%97%E6%8B%8D&autoload=true&count=20&en_qc=1&cur_tab=1&from=search_tab&pd=synthesis×tamp=1556371760933".format(j) #这就是今日头条的json页面,我们要爬取的数据都在者上面
driver.get(url=url)
text = driver.page_source #这边得到的数据不是个干净的json格式字符串,而是以html标签包裹的字符串,所以当用json.loads 处理时会报错。
pattern1 = re.compile(r'<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">') #一个正则表达式
out1 = re.sub(pattern1,'',text) #通过re.sub ,用’‘来替换在text中匹配到的字符串,这样就做到了把不干净的html标签清除 下面同理
# print(out1)
pattern =re.compile(r'</pre></body></html>')
data =re.sub(pattern,'',out1)
datad = json.loads(data)
# print(datad)
shuju = datad['data'] #直接选出data 中的内容
for iin shuju:
print(i.get('abstract','')) #因为data中的内容是字典形式,而且是大字典中包含小字典,所以用for 遍历每个小字典,再用字典的.get 查询每个小字典的相应 键对。.get(’查询的键‘ ,’默认的方式‘) (ps,默认的方式,就是当你字典中查不到出错时,就默认为空。来防止程序中断执行报错)
print(i.get('image_list',''))