python爬虫基础(二)

requests+selenium+scrapy

python爬虫基础(一)

python爬虫

1、异步爬虫

目的:在爬虫中使用异步实现高性能的数据爬取操作

requests.get()方法是一个阻塞的方法

异步爬虫之多进程and多线程(不建议使用)

多线程、多进程:

  1. 好处:可以为相关阻塞的操作单独开启线程或者进程,阻塞操作就可以异步执行
  2. 弊端:无法无限制的开启多线程或者多进程
异步爬虫之线程池and进程池(适当使用)

线程池、进程池:

  1. 好处:可以降低系统对进程或者线程创建销毁的一个频率,从而降低系统的开销
  2. 弊端:池中线程或进程的数量是有上限的
# 单线程
import time

def get_page(string):
    print(f"正在下载:{string}")
    time.sleep(2)
    print("下载成功")

names=['a','b','c','d']

start=time.time()

for i in range(len(names)):
    get_page(names[i])

end=time.time()
print(f"{end-start} second")
# 线程池

import time
from multiprocessing.dummy import Pool


def get_page(string):
    print(f"正在下载:{string}")
    time.sleep(2)
    print("下载成功")


names = ['a', 'b', 'c', 'd']

# 实例化线程池对象
pool=Pool(4)
pool.map(get_page,names)
单线程+异步协程(推荐)

event_loop:事件循环,相当于一个无限循环,我们可以把一些函数注册到这个事件循环上,当满足某些条件的时候,函数就会被循环执行

coroutine:协程对象,我们可以将协程对象注册到事件循环中,它会被事件循环调用。我们可以使用async关键字来定义一个方法,这个方法在调用时不会立即被执行,而是返回一个协程对象

task:任务,它是对协程对象的进一步封装,包含了任务的各个状态

future:代表将来执行或还没有执行的任务,实际上和task没有本质区别

async:定义一个协程

await:用来挂起阻塞方法的执行

# 协程
import asyncio


async def request(url):
    print(f"正在请求的url是:{url}")
    print("请求成功")
    return url


# 被async修饰的函数,调用之后返回的一个协程对象
a = request('www.baidu.com')

# 创建事件循环对象
loop = asyncio.get_event_loop()

# 将协程对象注册到loop中,然后启动loop
loop.run_until_complete(a)
# task的使用
import asyncio


async def request(url):
    print(f"正在请求的url是:{url}")
    print("请求成功")
    return url


# 被async修饰的函数,调用之后返回的一个协程对象
a = request('www.baidu.com')

loop = asyncio.get_event_loop()
# 基于loop创建了一个task对象
task = loop.create_task(a)

loop.run_until_complete(task)
# future的使用
import asyncio


async def request(url):
    print(f"正在请求的url是:{url}")
    print("请求成功")
    return url


# 被async修饰的函数,调用之后返回的一个协程对象
a = request('www.baidu.com')

loop = asyncio.get_event_loop()

future = asyncio.ensure_future(a)

loop.run_until_complete(future)
# future的使用
import asyncio


async def request(url):
    print(f"正在请求的url是:{url}")
    print("请求成功")
    return url


# 被async修饰的函数,调用之后返回的一个协程对象
a = request('www.baidu.com')

def callback_func(task):
    # result返回的就是任务对象中封装的协程对象对应函数的返回值
    print(task.result())


# 绑定回调
loop = asyncio.get_event_loop()
future = asyncio.ensure_future(a)

# 将回调函数绑定到任务对象中
future.add_done_callback(callback_func)
loop.run_until_complete(future)
补充:回调函数

定义一个函数,然后将这个函数的函数名传递给另一个函数做参数,以这个参数命名的函数就是回调函数


例如:有一家旅馆提供叫醒服务,但是要求旅客自己决定被叫醒的方法。可以是客房打电话,也可以是派服务员去敲门。“叫醒”这个行为是旅馆提供的,但是叫醒的方式是由旅客决定并告诉旅馆的,也就是回调函数


def way(arg: str):
    print(arg)


def service(arg: str, callback):
    callback(arg)


service('请打电话叫我起床', way)
# 带额外状态信息的回调函数
def add(x, y):
    return x + y


class ResultHandler(object):
    def __init__(self):
        self.sequence = 0

    def handle(self, result):
        self.sequence += 1
        print(f"{self.sequence} got: {result}")


def apply_async(func, args, *, callback):
    result = add(*args)
    callback(result)


r = ResultHandler()
apply_async(add, (1, 2), callback=r.handle)
# 使用协程
def add(x, y):
    return x + y


def apply_async(func, args, *, callback):
    result = add(*args)
    callback(result)


def make_handler():
    sequence = 0
    while True:
        result = yield
        sequence += 1
        print(f"[{sequence}] got: {result}")


handler = make_handler()
next(handler)
apply_async(add, (2, 3), callback=handler.send)
补充:yield

yield可以看成return,但yield的作用不等于return。把yield看成return之后,再将yield看成生成器(generator)的一部分

def ret():
    print("starting")
    while True:
        value = yield 4
        print(f"value: {value}")


generator = ret()
print(next(generator))
print("divider")
print(next(generator))

在这里插入图片描述

1、程序开始执行以后,由于ret()函数中包含yield关键字,所以ret()函数并不会真正被执行(发现是不是和协程的特点很像),而是先得到一个生成器generator(一个class对象)

2、直到调用next()方法,ret()函数正式开始执行,先执行ret()函数中的print方法,然后进入while循环

3、程序遇到yield关键字,return出一个4(相当于是函数的一个返回值),然后程序停止,并没有对value执行赋值操作,此时next(generator)语句执行完成,输出的内容分别是"starting"和"4"

4、程序执行print(“divider”)

5、又开始执行下面的print(next(generator)),此时是从上面的next()方法执行结束之后程序停止的地方开始执行的,也就是执行对value的赋值操作,但这个时候赋值操作的右边是没有值的(因为刚才那个4已经return出去了),所以这个时候value赋值为None

6、然后程序继续执行while语句,再次遇到yield时,return出去4,程序停止执行

def ret():
    print("starting")
    while True:
        value = yield 4
        print(f"value: {value}")


generator = ret()
print(next(generator))
print("divider")
print(generator.send(2))

在这里插入图片描述

send()方法可以向yield所在行的变量发送一个值,同时send()方法也包含next()方法的功能

7、程序执行generator.send(2),程序会从yield关键字所在行继续向下执行,同时send会将2这个值赋值给value变量

8、send()方法中包含next()方法,所以程序会继续向下执行,直到程序再次遇到yield关键字,yield在返回后面的值后,程序再次暂停,直到再次调用next()方法或send()方法

对于生成器的相关概念可以参考:一篇文章入门python基础

多任务异步协程

进程控制:七状态模型

在这里插入图片描述

import asyncio
import time


async def request(url):
    print(f"正在下载:{url}")
    # 在异步协程中如果出现了同步模块相关的代码,就无法实现异步
    # time.sleep(2)

    # 异步模块
    # 在asyncio中遇到阻塞操作时必须进行手动挂起
    await(asyncio.sleep(2))
    print(f"下载结束:{url}")


urls = {
    'www.baidu.com',
    'www.sougou.com',
    'www.hnu.edu.cn'
}

# 任务列表:存放多个任务对象
futures = []

for url in urls:
    c = request(url)

    # future的使用
    future = asyncio.ensure_future(c)
    futures.append(future)

# 创建事件循环对象
loop = asyncio.get_event_loop()

# 需要将任务列表封装到wait中
loop.run_until_complete(asyncio.wait(futures))
aiohttp模块

对应的flask:

from flask import Flask
import time

app = Flask(__name__)


@app.route('/hyh')
def index_hyh():
    time.sleep(2)
    return "霍雨浩"


@app.route('/twt')
def index_twt():
    time.sleep(2)
    return "唐舞桐"


@app.route('/gyn')
def index_gyn():
    time.sleep(2)
    return "古月娜"


if __name__ == "__main__":
    app.run()

如果对flask不太了解,可以参考:Flask入门(一)Flask入门(二)模板flask入门(三)静态文件

import requests
import asyncio
import time
import aiohttp

start=time.time()
urls = [
    'http://127.0.0.1:5000/twt',
    'http://127.0.0.1:5000/hyh',
    'http://127.0.0.1:5000/gyn'
]

async def request(url):

    # requests.get发起的请求基于同步,必须使用基于异步的网络请求模块进行指定url的请求发送
    # aiohttp:基于异步网络请求的模块
    # response=requests.get(url=url)

    async with aiohttp.ClientSession() as session:

        # get()、post()
        # headers:UA伪装、params/data:参数处理、proxy="http://ip:port"
        async with await session.get(url) as response:
            # text():返回字符串类型的响应数据
            # read():返回二进制类型的响应数据
            # json():返回json对象

            # 获取响应数据之前一定要使用await进行手动挂起
            text=await response.text()

            print(text)

futures=[]

for url in urls:
    c=request(url)
    future=asyncio.ensure_future(c)
    futures.append(future)

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

end=time.time()

print(f"总耗时:{end-start}")

可能会遇到的异常及处理办法:Python - Task exception was never retrieved & AttributeError: aexit

2、selenium

selenium模块和爬虫之间的关联:

  1. 便捷地获取网站中动态加载的数据
  2. 便捷实现模拟登录

selenium模块:基于浏览器自动化的模块:通过编写一些python的相关代码,让这些python代码表示一些行为动作,让这些python代码所表示的行为动作触发到浏览器中,然后浏览器根据代码的指示完成相关的自动化操作(测试中也会使用selenium,如果大家感兴趣,可以自行百度)

selenium的使用流程:

  1. 环境的安装:pip install selenium
  2. 下载浏览器的驱动程序(谷歌浏览器的驱动程序)
    1. 驱动程序的路径:http://chromedriver.storage.googleapis.com/index.html
    2. 驱动程序和浏览器的映射关系:http://blog.csdn.net/huilan_same/article/details/51896672;或者直接在浏览器地址栏输入:chrome://version/
  3. 实例化一个浏览器对象
  4. 编写基于浏览器自动化的操作代码

火狐浏览器对应的驱动程序:python+selenium+firefox使用与部署详解

import time
from selenium import webdriver
driver = webdriver.Firefox(executable_path='./driver/geckodriver.exe')
driver.get("https://www.baidu.com/")
time.sleep(2)
html = driver.page_source
print(html)
driver.quit()
实例:爬取药监管理局信息

国家药品监督管理局化妆品生产许可信息管理系统服务平台:http://scxk.nmpa.gov.cn:81/xk/

from selenium import webdriver
from lxml import etree
import time

driver = webdriver.Firefox(executable_path='./driver/geckodriver.exe')
driver.get("http://scxk.nmpa.gov.cn:81/xk/")
time.sleep(2)
# page_source:获取浏览器当前页面的页面源码数据
html = driver.page_source

tree=etree.HTML(html)

contents=tree.xpath('//*[@class="hzblist"]/li')

for content in contents:
    title=content.xpath('./dl/@title')
    print(title)

driver.quit()
实例:淘宝之自动化操作
# -*- coding: utf-8 -*-
# @Time    : 2022/10/19 9:23
# @Author  : 楚楚
# @File    : 25淘宝.py
# @Software: PyCharm
from selenium import webdriver
import time

browser=webdriver.Firefox(executable_path="./driver/geckodriver.exe")


browser.get("https://www.taobao.com/")

# 标签定位
search_input=browser.find_element_by_id('q')
# 标签交互
search_input.send_keys("IPhone13")

# 执行一组js程序
browser.execute_script('window.scrollTo(0,document.body.scrollHeight)')

# 点击搜索按钮
btn=browser.find_element_by_css_selector('.btn-search')
btn.click()

browser.get('https://www.baidu.com/')
time.sleep(2)
# 回退
browser.back()
time.sleep(2)
# 前进
browser.forward()


browser.quit()

基于浏览器自动化的操作代码:

  1. 发起请求:get(url)
  2. 标签定位:find系列的方法
  3. 标签交互:send_keys(‘xxx’)
  4. 执行js程序execute_script(js代码)
  5. 前进、后退:back()、forward()
  6. 关闭浏览器:quit()
iframe处理+动作链
# -*- coding: utf-8 -*-
# @Time    : 2022/10/19 12:11
# @Author  : 楚楚
# @File    : 26iframe.py
# @Software: PyCharm
import time

from selenium import webdriver
from selenium.webdriver import ActionChains

driver=webdriver.Firefox(executable_path='./driver/geckodriver.exe')

driver.get("https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable")

# 如果定位的标签是存在于iframe标签中,则必须通过如下操作再进行标签定位
driver.switch_to.frame('iframeResult') # 切换浏览器标签定位的作用域
div=driver.find_element_by_id('draggable')

# 动作链
action=ActionChains(driver)
# 点击长按指定的标签
action.click_and_hold(div)
for i in range(5):
    # perform():立即执行动作链操作
    action.move_by_offset(17,0).perform()
    time.sleep(0.5)

action.release()

driver.quit()
  • 如果定位的标签存在于iframe标签中,则必须使用switch_to.frame(id属性值)切换浏览器的标签定位
  • 动作链:from selenium.webDriver import ActionChains
    • 实例化一个动作链对象:action=ActionChains(driver)
    • click_and_hold(标签名称):长按且点击操作
    • move_by_offset(x,y)
    • perform()让动作链立即执行
    • action.release()释放动作链对象
实例:EI检索
from selenium import webdriver
from time import sleep

driver=webdriver.Firefox(executable_path='./driver/geckodriver.exe')
driver.get("https://www.engineeringvillage.com/search/quick.url")

search=driver.find_element_by_id("search-word-1")
search.send_keys("industrial design")

button=driver.find_element_by_id('searchBtn')
button.click()

sleep(10)

driver.quit()
无头浏览器+规避检测
from selenium import webdriver
# 实现无可视化界面的
from selenium.webdriver.firefox.options import Options
# 规避检测
from selenium.webdriver import FirefoxOptions
from selenium.webdriver import FirefoxProfile

# 无可视化界面的操作
firefox_options = Options()
firefox_options.add_argument("--headless")
firefox_options.add_argument("--disable-gpu")

# 实现规避检测(这种方法不晓得生不生效)
options = FirefoxOptions()
profile = FirefoxProfile()
ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:105.0) Gecko/20100101 Firefox/105.0'
profile.set_preference("network.proxy.type", 4) #自动检测代理设置
profile.set_preference("dom.webdriver.enabled", False)  # 设置非driver驱动
profile.set_preference('useAutomationExtension', False)  # 关闭自动化提示
profile.update_preferences()  # 更新设置

driver = webdriver.Firefox(executable_path='./driver/geckodriver.exe', firefox_options=firefox_options, options=options,firefox_profile=profile)

# 无可视化界面(无头浏览器) phantomJs
driver.get("https://www.baidu.com")

print(driver.page_source)
with open('./baidu.html', 'w', encoding='utf-8') as file:
    file.write(driver.page_source)
driver.quit()
实例:百度

通用模板:

from selenium import webdriver
# 实现无可视化界面的
from selenium.webdriver.firefox.options import Options
# 规避检测
from selenium.webdriver import FirefoxOptions
from selenium.webdriver import FirefoxProfile

# 无可视化界面的操作
firefox_options = Options()
firefox_options.add_argument("--headless")
firefox_options.add_argument("--disable-gpu")

# 实现规避检测
options = FirefoxOptions()
profile = FirefoxProfile()
ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:105.0) Gecko/20100101 Firefox/105.0'
profile.set_preference("network.proxy.type", 4) #自动检测代理设置
profile.set_preference("dom.webdriver.enabled", False)  # 设置非driver驱动
profile.set_preference('useAutomationExtension', False)  # 关闭自动化提示
profile.update_preferences()  # 更新设置

driver = webdriver.Firefox(executable_path='./driver/geckodriver.exe', firefox_options=firefox_options, options=options,firefox_profile=profile)
from selenium import webdriver
# 实现无可视化界面的
from selenium.webdriver.firefox.options import Options
# 规避检测
from selenium.webdriver import FirefoxOptions
from selenium.webdriver import FirefoxProfile

from lxml import etree

# 无可视化界面的操作
firefox_options = Options()
firefox_options.add_argument("--headless")
firefox_options.add_argument("--disable-gpu")

# 实现规避检测
options = FirefoxOptions()
profile = FirefoxProfile()
ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:105.0) Gecko/20100101 Firefox/105.0'
profile.set_preference("network.proxy.type", 4)  # 自动检测代理设置
profile.set_preference("dom.webdriver.enabled", False)  # 设置非driver驱动
profile.set_preference('useAutomationExtension', False)  # 关闭自动化提示
profile.update_preferences()  # 更新设置

driver = webdriver.Firefox(executable_path='./driver/geckodriver.exe', firefox_options=firefox_options, options=options,
                           firefox_profile=profile)

driver.get("https://www.baidu.com/")

# search = driver.find_element_by_id("kw")
# search.send_keys("湖南大学")
#
# btn = driver.find_element_by_id("su")
# btn.click()

tree = etree.HTML(driver.page_source)
contents = tree.xpath('//*[@id="hotsearch-content-wrapper"]/li')
for content in contents:
    href = content.xpath('./a/@href')[0]
    title = content.xpath('./a/span[2]/text()')[0]
    print((href, title))

    driver.get(href)
    sub_tree = etree.HTML(driver.page_source)
    data = sub_tree.xpath(
        '/html/body/div[2]/div[4]/div[1]/div[3]/div[1]/div[1]/div/div/div/div/div[2]/p[1]/a/em/text()')[0]
    print(data)

参考

1、python3回调函数(callback)

2、彻底弄懂Python中的回调函数(callback)

3、python中yield的用法详解——最简单,最清晰的解释

4、python生成器和迭代器的区别

5、火狐真机绕过selenium检测

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值