生产者消费者模型
import threading
import requests
from lxml import etree
import os
from urllib import request
from queue import Queue
# 生产者模型 -- 储存图片连接
class Producer(threading.Thread):
headers = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36",
}
def __init__(self, page_queue, img_queue, *args, **kwargs):
super(Producer, self).__init__(*args, **kwargs)
self.page_queue = page_queue
self.img_queue = img_queue
def run(self):
while True:
if self.page_queue.empty():
break
url = self.page_queue.get()
self.parse_page(url)
def parse_page(self, url):
response = requests.get(url=url,headers=self.headers)
text = response.text
html = etree.HTML(text)
img_list = html.xpath('//div[@class="page-content text-center"]/div/a/img')
for img in img_list:
img_url = img.xpath('./@data-original')[0]
img_name = img.xpath('./@alt')[0]+'.jpg'
self.img_queue.put((img_url, img_name))
# 消费者模型 -- 保存图片
class Consumer(threading.Thread):
def __init__(self, page_queue, img_queue, *args, **kwargs):
super(Consumer, self).__init__(*args, **kwargs)
self.page_queue = page_queue
self.img_queue = img_queue
def run(self):
while True:
if self.page_queue.empty() and self.img_queue.empty():
break
img_url, img_name = self.img_queue.get()
request.urlretrieve(img_url, "imgs/" + img_name)
print(img_name + " 下载完成!")
# 定义一个主方法,该方法向处理方法中传值
def main():
page_queue = Queue(50) #存储页码链接队列
img_queue = Queue(100)#存储解析出来的图片链接队列
#想要爬取前10也的数据
for x in range(1, 11):
url = "https://www.doutula.com/photo/list/?page=%d" % x
page_queue.put(url) #将10页的页码链接加入到了page_queue
for x in range(3):
t = Producer(page_queue, img_queue)
t.start()
for x in range(3):
t = Consumer(page_queue, img_queue)
t.start()
if __name__ == '__main__':
main()
selenium
1.简介: 基于浏览器自动化的模块。
2.环境安装:pip install selenium
3.准备工作:
- 安装一款浏览器
- 下载一个浏览器的驱动程序(网上可以直接搜到)
- http://chromedriver.storage.googleapis.com/index.html -- 谷歌
4.selenium和爬虫之间的关联
- 便捷的抓取页面中的数据(可见既可得)
- 实现模拟登录
1.selenium基本操作
from selenium import webdriver
from time import sleep
# 1.实例化一款浏览器对象 -- 参数是浏览器驱动路径
browser = webdriver.Chrome(executable_path='chromedriver.exe')
# 2.对指定的url发起请求
browser.get('https://www.jd.com/')
sleep(2)
# 3.进行标签定位 -- 单节点获取
search_box = browser.find_element_by_xpath('//*[@id="key"]')
'''
多节点获取:
lis = browser.find_elements_by_xpath('//*[@id="key"]') # 注意是elements多个s
print(lis) # 输出为列表
'''
'''
输入文字用send_keys()
清空文字用clear()
点击按钮用click()
'''
# 4.向定位到的标签中录入文本信息
search_box.send_keys('华为手机')
sleep(2)
# 5.触发点击
browser.find_element_by_xpath('//*[@id="search"]/div/div[2]/button').click()
sleep(2)
# js注入 -- 滑轮滚动
browser.execute_script('window.scrollTo(0,document.body.scrollHeight)')
sleep(2)
# 关闭浏览器
# browser.quit()
browser.close()
2.selenium 动态加载数据
# 药监局数据获取 -- http://scxk.nmpa.gov.cn:81/xk/
from selenium import webdriver
from time import sleep
from lxml import etree
# 1.实例化浏览器对象
browser = webdriver.Chrome(executable_path='chromedriver.exe')
sleep(1) # 睡眠一下加载数据
# 2.发起请求
browser.get('http://scxk.nmpa.gov.cn:81/xk/')
# 3.获取页面原码数据
page_text = browser.page_source
page_text_list = [page_text]
for i in range(5):
# 点击下一页获取页面数据数据
browser.find_element_by_xpath('//*[@id="pageIto_next"]').click()
sleep(1) # 睡眠一下加载数据
page_text_list.append(browser.page_source)
for page_text in page_text_list:
tree = etree.HTML(page_text) # 实例化对象
li_list = tree.xpath('//*[@id="gzlist"]/li') # 获取指定标签数据
for li in li_list:
title = li.xpath('./dl/@title')[0]
print(title)
browser.close() # 关闭浏览器
3.动作连
from selenium import webdriver
from time import sleep
from selenium.webdriver import ActionChains # 引入动作连
#实例化一款浏览器对象
bro = webdriver.Chrome(executable_path='chromedriver.exe')
bro.get('https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable')
sleep(1)
#注意:如果定位的标签是出现在iframe标签之中,则定位失败,需要完成如下操作
bro.switch_to.frame('iframeResult') # 用iframe的id获取位置
div_tag = bro.find_element_by_xpath('//*[@id="draggable"]')
#1.实例化一个动作连对象,且将其和指定的浏览器进行关联
action = ActionChains(bro)
action.click_and_hold(div_tag) #点击且长按
for i in range(5):
action.move_by_offset(15,17).perform() #perform表示让动作连立即执行
sleep(0.5)
# 释放动作连
action.release()
sleep(1)
bro.close()
4.模拟登陆
安装图片处理工具:
pip install Pillow
# 12306模拟登陆 : https://kyfw.12306.cn/otn/resources/login.html
from selenium import webdriver
from time import sleep
import base64
import json
import requests
#pip install Pillow
from PIL import Image # 引入图片处理
from selenium.webdriver import ActionChains #动作连
# 验证码识别函数
def base64_api(uname, pwd, img,typeid):
with open(img,'rb') as f:
base64_data = base64.b64encode(f.read())
b64 = base64_data.decode()
data = {"username": uname, "password": pwd,"typeid":typeid, "image": b64}
result = json.loads(requests.post("http://api.ttshitu.com/predict", json=data).text)
if result['success']:
return result["data"]["result"]
else:
return result["message"]
return ""
#实例化一款浏览器对象
bro = webdriver.Chrome(executable_path='chromedriver.exe')
bro.get('https://kyfw.12306.cn/otn/resources/login.html')
sleep(2)
browser.maximize_window() # 浏览器最大化(全屏)
bro.find_element_by_xpath('/html/body/div[2]/div[2]/ul/li[2]/a').click()
sleep(2)
bro.find_element_by_xpath('//*[@id="J-userName"]').send_keys('bobo123456')
sleep(2)
bro.find_element_by_xpath('//*[@id="J-password"]').send_keys('1234567890')
sleep(2)
# 验证码操作 -- Windows显示 - 页面与布局需要调到100% 才能截出完整的图片
# 1.将当前整个页面进行图片保存
bro.save_screenshot('./main.png')
# 2.制定裁剪区域
img_tag = bro.find_element_by_xpath('//*[@id="J-loginImg"]')
location = img_tag.location # 获取标签的左下角坐标
size = img_tag.size # 获取标签的尺寸(宽 - 高)
# rangle就是裁剪区域
rangle = (int(location['x']),int(location['y']),int(location['x']+size['width']),int(location['y']+size['height']))
# 裁剪出验证码图片
i = Image.open('./main.png')
frame = i.crop(rangle)
frame.save('./code.png')
# 3.通过打码平台识别验证码
img_path = "./code.png"
result = base64_api(uname='bb328410948', pwd='bb328410948', img=img_path,typeid=21)
print(result) #识别验证码的结果:259,141|28,160
# 259,141|28,160 ==》 [[x,y],[x,y]]
# 将识别的结果转化为列表形式
all_list = []
if '|' in result:
list_1 = result.split('|')
count_1 = len(list_1)
for i in range(count_1):
xy_list = []
x = int(list_1[i].split(',')[0])
y = int(list_1[i].split(',')[1])
xy_list.append(x)
xy_list.append(y)
all_list.append(xy_list)
else:
x = int(result.split(',')[0])
y = int(result.split(',')[1])
xy_list = []
xy_list.append(x)
xy_list.append(y)
all_list.append(xy_list)
# 4.按照all_list中的位置进行定向点击
for loc in all_list:
x = loc[0]
y = loc[1]
ActionChains(bro).move_to_element_with_offset(img_tag,x,y).click().perform()
sleep(0.5)
# 点击登陆
bro.find_element_by_xpath('//*[@id="J-login"]').click()
sleep(1)
bro.quit()
爬虫逆向
思考:如何在Python程序中执行一个js函数,且获取返回值?
PyExecJS介绍:PyExecJS 是一个可以使用 Python 来模拟运行 JavaScript 的库。
我们需要pip install PyExecJS对其进行环境安装。
安装nodejs的开发环境
JS混淆:将核心的js函数的实现进行了加密。
JS反混淆:暴力破解:http://www.bm8.com.cn/jsConfusion/
- 发条js调试工具
1.气象数据爬取
# 网站: https://www.aqistudy.cn//html/city_detail.php?v=1.10
''''
分析:
1.该网站是禁用开发者工具
2.当修改了查询条件后,点击查询按钮,发起了Ajax请求,请求到了我们想要爬取的数据。
- 如果我们能找到ajax请求的代码,则可以从中提取到:
- 请求的url
- 请求的方式
- 请求参数
- 对请求到的数据进行的哪些操作
- 通过火狐浏览器可以找到搜索按钮绑定的点击事件对应的函数:getData()
- 需要在谷歌浏览器中捕获数据包,全局搜索getData的实现代码,希望从中能找到ajax请求代码。
- 分析getData函数实现代码:
- 在改函数内部没有发现ajax代码,但是发现了另外两个函数的调用。
- type=="HOUR"
- 另两个函数中没有找到ajax代码,发现了另一个函数的调用
sJwf3VwkSgqmf0Ddr92K(method,param,函数,0.5)
method = 'GETDETAIL' or 'GETCITYWEATHER'
param = {4个查询条件}
- 寻找sJwf3VwkSgqmf0Ddr92K函数的定义:
- 在该函数的定义中找到了ajax请求代码:
- url:https://www.aqistudy.cn/apinew/aqistudyapi.php
- type:post
- 参数:{ h0lgYxgR3: param },param是动态变化,param = pNg63WJXHfm8r(method, object)
思考:如何在Python程序中执行一个js函数,且获取返回值?
PyExecJS介绍:PyExecJS 是一个可以使用 Python 来模拟运行 JavaScript 的库。
我们需要pip install PyExecJS对其进行环境安装。
安装nodejs的开发环境
JS混淆:将核心的js函数的实现进行了加密。
JS反混淆:暴力破解:- 发条js调试工具
'''
import requests
import execjs
node = execjs.get() #实例化一个node对象
ctx = node.compile(open('./test.js',encoding='utf-8').read())# 读取js文件
method = 'GETCITYWEATHER'
city = '北京'
type = 'HOUR'
start_time = '2018-01-25 00:00:00'
end_time = '2018-01-25 23:00:00'
jsFuncName = 'getPostParamCode("{0}", "{1}", "{2}", "{3}", "{4}")'.format(method, city, type, start_time, end_time)
params = ctx.eval(jsFuncName) # 执行js代码
# print(params)
url = 'https://www.aqistudy.cn/apinew/aqistudyapi.php'
headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36',
'Referer': 'https://www.aqistudy.cn//html/city_detail.php?v=1.10',
'Cookie':'dcity=%E5%8C%97%E4%BA%AC; UM_distinctid=178f76b93081fd-0840408d87ce26-5771031-1fa400-178f76b930a31b; CNZZDATA5808503=cnzz_eid%3D1742014539-1619057731-%26ntime%3D1619057731'
}
params_dic = {
'h0lgYxgR3':params
}
response_text = requests.get(url,headers=headers,params=params_dic).text
# print(response_text)
#解密
de = 'dX506x9jVK3vuMMhoz6ZXx("{0}")'.format(response_text)
text = ctx.eval(de)
print(text)
2.有道翻译在线
'''
需要携带的数据
i: hello
from: AUTO
to: AUTO
smartresult: dict
client: fanyideskweb
salt: 16191634764322
sign: 1fe112b4e634255c0820d6bc99d6f083
lts: 1619163476432
bv: 7b7290f9dbb825e1eb29882d0b1fd770
doctype: json
version: 2.1
keyfrom: fanyi.web
action: FY_BY_REALTlME
'''
import random
import requests
import execjs
import time
'''
分析:根据比对发现翻译数据包中只有三个参数是动态变化的.
- 注意:python的时间戳是10位的,而js是13位。如果将python的时间戳乘以1000保留整数位就得到了标准的js时间戳
salt:是lts加了一个个位数
sign:加密数据
lts:是一个标准的js时间戳
'''
# 如果请求不成功,把4个响应头键值对都填上,一般都能成功
headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36',
'Cookie':'P_INFO=yinghelaile; OUTFOX_SEARCH_USER_ID=-1061433457@10.108.160.101; OUTFOX_SEARCH_USER_ID_NCOO=4275058.306269712; JSESSIONID=aaan4gy4BLSmv8YLmy8Jx; ___rl__test__cookies=1619163476428',
'Referer': 'https://fanyi.youdao.com/',
'Connection': 'keep-alive',
}
url = 'https://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule'
lts = int(time.time()*1000) # js时间戳
salt = int(str(lts)+str(int(random.random()*10))) # 比lst多加一个随机数
word = input('请输入要翻译的单词:')
node = execjs.get() # 实例化node对象
# 加载发条工具调试后的js代码
ctx = node.compile(open('./youdao.js','r',encoding='utf-8').read())
func = 'sign("{0}","{1}")'.format(word,salt)
sign = ctx.eval(func) # 执行js函数
# print(sign)
data = {
'i': word,
'from': 'AUTO',
'to': 'AUTO',
'smartresult': 'dict',
'client': 'fanyideskweb',
'salt': salt,
'sign': sign,
'lts': lts,
'bv': '7b7290f9dbb825e1eb29882d0b1fd770',
'doctype': 'json',
'version': 2.1,
'keyfrom': 'fanyi.web',
'action': 'FY_BY_REALTlME',
}
# 发起请求获取响应数据
json_data = requests.post(url,headers=headers,data=data).json()
translate = json_data['translateResult'][0][0]['tgt']
print(translate)