单线程+多任务异步协程、浏览器自动化

# 梨视频数据的爬取
import requests
from lxml import etree
import re
headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36'
}
url = 'https://www.pearvideo.com/category_1'
page_text = requests.get(url,headers=headers).text
tree = etree.HTML(page_text)
li_list = tree.xpath('//*[@id="listvideoListUl"]/li')
for li in li_list:
    detail_url = 'https://www.pearvideo.com/'+li.xpath('./div/a/@href')[0]
    title = li.xpath('./div/a/div[2]/text()')[0]+'.mp4'
    detail_page_text = requests.get(detail_url,headers=headers).text
    # 视频地址是由js代码动态生成的,只能用正则解析
    ex = 'srcUrl="(.*?)",vdoUrl'
    video_url = re.findall(ex,detail_page_text,re.S)[0]
    video_data = requests.get(video_url,headers=headers).content
    with open(title,'wb'as fp:
        fp.write(video_data)

multiprocessing模块

import time
from time import sleep

start = time.time()
urls = [
    'www.a.com',
    'www.b.com',
    'www.c.com',
]

def get_request(url):
    print("正在下载:",url)
    sleep(2)
    print('下载结束:',url)

for url in urls:
    get_request(url)

print("总耗时为:",time.time()-start)  # 6.0057127475738525s
from multiprocessing.dummy import Pool

start = time.time()

# 开启三个线程
pool = Pool(3)

# map有两个参数,第一个是自定义函数,第二个是列表。    
# 作用:函数对列表当中的每一个元素进行相关操作。    
pool.map(get_request,urls)

print("总耗时为:",time.time()-start)  # 2.0103282928466797s
from flask import Flask
from time import sleep
app = Flask(__name__)


@app.route('/1')
def index_bobo():
    sleep(2)
    return 'Hello 1'

@app.route('/2')
def index_jay():
    sleep(2)
    return 'Hello 2'

@app.route('/3')
def index_tom():
    sleep(2)
    return 'Hello 3'

if __name__ == '__main__':
    app.run(threaded=True)
import requests
start = time.time()
urls = [
    'http://localhost:5000/1',
    'http://localhost:5000/2',
    'http://localhost:5000/3',
]
def get_request(url):
    page_text = requests.get(url).text
    print(page_text)

pool = Pool(3)
pool.map(get_request,urls)
print(time.time()-start)   # 4.036084413528442

单线程+多任务异步协程

协程

  • 在函数(特殊的函数)定义的时候,如果使用了async修饰的话,则改函数调用后会返回一个协程对象,并且函数内部的实现语句不会被立即执行

任务对象

  • 任务对象就是对协程对象的进一步封装。任务对象==高级的协程对象==特殊的函数
  • 任务对象时必须要注册到事件循环对象中
  • 给任务对象绑定回调:爬虫的数据解析

事件循环

  • 当做是一个容器,容器中必须存放任务对象。
  • 当启动事件循环对象后,则事件循环对象会对其内部存储任务对象进行异步的执行。

回调函数

  • task.result()接受特殊函数的返回值(任务对象的协程对象的特殊函数的返回值)

执行顺序:
开启事件循环-->执行任务对象-->遇到阻塞-->切换执行的任务对象-->任意任务对象执行完成-->调用该任务对象的回调函数

aiohttp:支持异步网络请求的模块

# 协程
import asyncio
def callback(task):# 作为任务对象的回调函数
    # task.result()接受特殊函数的返回值:haha
    print('i am callback and ',task.result())

async def test():
    print('i am test()')
    return 'haha'

# c是协程对象
c = test()
# <coroutine object test at 0x0000018119ACAB48>

# 传入协程对象,封装了一个任务对象
task = asyncio.ensure_future(c)


task.add_done_callback(callback)


# 创建一个事件循环的对象
loop = asyncio.get_event_loop()
# 任务对象注册到事件循环对象中
loop.run_until_complete(task)
# 多任务
import asyncio
import time

start = time.time()

async def get_request(url):

    # 在特殊函数内部的实现中不可以出现不支持异步的模块代码
    # await time.sleep(2)
    # await:必须等到阻塞代码执行完后再执行后面的代码  
    await asyncio.sleep(2)
    print('下载成功:',url)

urls = [
    'www.1.com',
    'www.2.com'
]
tasks = []
for url in urls:
    c = get_request(url)
    task = asyncio.ensure_future(c)
    tasks.append(task)

loop = asyncio.get_event_loop()

# loop.run_until_complete(asyncio.wait(tasks)),这是多任务,需要对任务进行挂起,  
# 注意:挂起操作需要手动处理
loop.run_until_complete(asyncio.wait(tasks))
print(time.time()-start)   # 2.0003480911254883
# 在爬虫中的应用
import aiohttp
import time
import asyncio
from lxml import etree

s = time.time()
urls = [
    'http://127.0.0.1:5000/1',
    'http://127.0.0.1:5000/2'
]

# import requests
# async def get_request(url):
#     requests不能实现异步
#     page_text = requests.get(url).text
#     return page_text

# 特殊的函数:请求发送和响应数据的捕获
async def get_request(url):

    # with环境资源管理器,可以不用手动关闭相关的资源。ClientSession:实例化一个Session对象
   async with aiohttp.ClientSession() as s:
        # get参数与requests模块几乎相同,如#s.get(url,headers,proxy="http://ip:port",params),proxy(代理)
        # ,get会阻塞,所以需要await
       async with await s.get(url=url) as response:
           # 获取响应数据,也要await
           page_text = await response.text() # read()返回的是byte类型的数据
           print(page_text)
# 细节:在每一个with前加上async,在每一个阻塞操作的前加上await
   return page_text

#回调函数
def parse(task):
    page_text = task.result()
    tree = etree.HTML(page_text)
    parse_data = tree.xpath('//text()')
    print(parse_data)


tasks = []
for url in urls:
    c = get_request(url)
    task = asyncio.ensure_future(c)
    task.add_done_callback(parse)
    tasks.append(task)

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))

print(time.time()-s)  # 2.0419085025787354

selenium模块在爬虫中的使用

selenium概念:是一个基于浏览器自动化的模块。
爬虫之间的关联:

  • 便捷的捕获到动态加载到的数据。(可见即可得)
  • 实现模拟登陆
  • 缺点:慢,需要打开浏览器再发送请求。

环境安装:pip install selenium -i https://pypi.tuna.tsinghua.edu.cn/simple

驱动安装:

  • 下载适合自己的浏览器的驱动程序:http://npm.taobao.org/mirrors/chromedriver/这个网址是下载谷歌浏览器的,其他浏览器请自行搜索。
  • 驱动下载好之要与代码脚本放在同一目录下。

基本使用

from selenium import webdriver
from time import sleep

# 实例化浏览器对象,后面是你的浏览器驱动位置,,'r':防止字符转义
driver = webdriver.Chrome(executable_path='chromedriver.exe')

# 若没有将浏览器设置为系统默认的,需要自定义浏览器位置
# options = webdriver.ChromeOptions()
# options.binary_location = r"浏览器位置Chrome.exe"
# browser = webdriver.Chrome(options=options)

# 用get打开百度页面
driver.get("http://www.baidu.com")

# 进行标签定位找到百度的输入框
search_input = driver.find_element_by_id('kw')
# 并输入 雪景
search_input.send_keys('雪景')
sleep(2)
# driver.find_element_by_xpath(),根据xpath表达式定位
# driver.find_element_by_name(),根据标签名称定位
# driver.find_element_by_class_name()

# 点击搜索按钮
driver.find_element_by_id('su').click()
# 进行页面跳转,等待2秒。
sleep(2)

# 执行js(js注入),滚轮向下一屏高度
driver.execute_script('window.scrollTo(0,document.body.scrollHeight)')
sleep(2)

# 获取整张页面源码数据
page_text = driver.page_source
print(page_text)
# 关闭浏览器
driver.quit()

selenium爬取动态加载的数据

from selenium import webdriver
from lxml import etree

driver = webdriver.Chrome(executable_path='chromedriver.exe')

driver.get('http://125.35.6.84:81/xk/')
sleep(1)
page_text = driver.page_source
page_text_list = [page_text]

for i in range(3):
    # 点击下一页
    driver.find_element_by_id('pageIto_next').click()
    sleep(1)
    page_text_list.append(driver.page_source)

for page_text in page_text_list:
    tree = etree.HTML(page_text)
    li_list = tree.xpath('//ul[@id="gzlist"]/li')
    for li in li_list:
        title = li.xpath('./dl/@title')[0]
        num = li.xpath('./ol/@title')[0]
        print(title+':'+num)

sleep(2)
driver.quit()

动作链

  • 一系列连续的动作,实现动作链需要导入模块:from selenium.webdriver import ActionChains

  • 在实现标签定位时,如果发现定位的标签是存在于iframe标签之中的,则在定位时必须执行一个固定的操作:driver.switch_to.frame('id'),id:iframe标签的id

动作链基本操作

from selenium.webdriver import ActionChains

driver = webdriver.Chrome(executable_path='chromedriver.exe')
driver.get('https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable')

driver.switch_to.frame('iframeResult')
div_tag = driver.find_element_by_id('draggable')

# 拖动= 点击+滑动
# 实例化动作链对象,传入页面对象
action = ActionChains(driver)
action.click_and_hold(div_tag)

for i in range(5):
    # perform让动作链立即执行。17:水平移动,5:垂直移动
    action.move_by_offset(17,5).perform()
    # action.move_to_element()  移动到哪一标签
    sleep(0.5)

# 回收机制,可有可无
action.release()

sleep(3)

driver.quit()

12306模拟登陆

pip install Pillow -i https://pypi.tuna.tsinghua.edu.cn/simple Pillow模块会自动附带下载PIL模块

# 超级鹰图片验证
import requests
from hashlib import md5

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()
from PIL import Image

driver = webdriver.Chrome(executable_path='chromedriver.exe')
driver.get('https://kyfw.12306.cn/otn/login/init')
sleep(5)
# 截屏,获取整个页面的图片
driver.save_screenshot('main.png')

# 定位到验证图片对应的标签
code_img_tag = driver.find_element_by_xpath('//*[@id="loginForm"]/div/ul[2]/li[4]/div/div/div[3]/img')
# img左上角的坐标
location = code_img_tag.location # {‘x’:274,'y':274}
# height:img的高
size = code_img_tag.size # {'height':190,'width':293}

# 裁剪的区域范围:左上角坐标,右下角坐标
rangle = (int(location['x']),int(location['y']),int(location['x']+size['width']),int(location['y']+size['height']))

# PIL模块打开图片
i = Image.open('./main.png')
# 使用crop方法裁剪图片,Image.crop(left, up, right, below)
frame = i.crop(rangle)
# 保存图片
frame.save('code.png')
def get_text(imgPath,imgType):
    chaojiying = Chaojiying_Client('chongxiao''chongxiao''999123')
    im = open(imgPath, 'rb').read()
    return chaojiying.PostPic(im, imgType)['pic_str']


result = get_text('./code.png',9004#返回两个坐标 55,70|267,133 ==[[55,70],[33,66]]
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)
print(all_list)# 将坐标转换为[[55,70],[33,66]]类似形式


# 使用动作链对象点击图片中的位置action = ActionChains(driver)
for a in all_list:
    x = a[0]
    y = a[1]
    # 点击
    ActionChains(driver).move_to_element_with_offset(code_img_tag,x,y).click().perform()
    sleep(1)
# 若释放时间过快会导致点击不成功,所有这里不再释放,也可暂停一段时间再释放
# ActionChains(driver).release()

driver.find_element_by_id('username').send_keys('123456')
sleep(1)
driver.find_element_by_id('password').send_keys('123456')
sleep(1)
driver.find_element_by_id('loginSub').click()

sleep(5)
driver.quit()

无头浏览器的操作

无头浏览器的操作:无可视化界面的浏览器:谷歌无头浏览器。

# 使用谷歌无头浏览器
from selenium import webdriver
from time import sleep
# 要导入一个Options的类
from selenium.webdriver.chrome.options import Options
# 实例化一个对象
chrome_options = Options()
# 做一些设置,这些是设置是固定的格式
chrome_options.add_argument('--headless')
chrome_options.add_argument('--disable-gpu')

# 将实例化好的对象传入浏览器对象中
driver = webdriver.Chrome(r'chromedriver.exe',options=chrome_options)
driver.get('https://www.cnblogs.com/')
print(driver.page_source)

让selenium规避检测

js代码window.navigator.webdriver
若返回的是undefined,则请求为正常请求;
若返回为True,则请求是用selenium发起的。
所以网站门户可以监测该返回值来确定请求是否正常。
规避监测方法:

from selenium import webdriver
# 需要导入模块
from selenium.webdriver import ChromeOptions

option = ChromeOptions()
option.add_experimental_option('excludeSwitches', ['enable-automation'])
#隐式等待设置为20秒
driver.implicitly_wait(time_to_wait=20)
driver = webdriver.Chrome(executable_path=r'chromedriver.exe',options=option)
driver.get('https://www.baidu.com/')
#print(driver.page_source)

移动端数据的爬取

fiddler是一款抓包工具:本质是一种代理服务器

  • 配置:让其可以抓取https协议的请求,默认情况只能抓取http协议请求。tools--&gt;options--&gt;https--&gt;安装证书(点击Decrypt HTTPS traffic)

证书:一种加密方式:

  • http:客户端和服务器端进行数据交互的某种形式
  • https:安全的http协议,https的加密方式采用的是证书密钥加密。

抓移动端数据包配置:

  1. 配置下fiddler的端口:tools-->options-->Connections-->Allow remote computers to connect,并记下此处fiddler的端口号。
  2. 将手机和fiddler所在的电脑处在同一个网段下(pc开启wifi,手机连接)
  3. 在手机中访问fiddler的ip:port;ip(cmd--ipconfig-->IPv4 地址),在当前页面中点击对应的连接下载证书
  4. 在手机中安装且信任证书
  5. 设置手机网络的代理:开启代理-->fiddler对应pc端的ip地址和fiddler自己端口号
posted @ 2020-08-11 00:08  虫萧  阅读(176)  评论(0编辑  收藏  举报