3. selenium


生产者消费者模型

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)

原文地址:https://www.cnblogs.com/jia-shu/p/14695520.html