文章目录
requests+selenium+scrapy
python爬虫
1、异步爬虫
目的:在爬虫中使用异步实现高性能的数据爬取操作
requests.get()方法是一个阻塞的方法
异步爬虫之多进程and多线程(不建议使用)
多线程、多进程:
- 好处:可以为相关阻塞的操作单独开启线程或者进程,阻塞操作就可以异步执行
- 弊端:无法无限制的开启多线程或者多进程
异步爬虫之线程池and进程池(适当使用)
线程池、进程池:
- 好处:可以降低系统对进程或者线程创建销毁的一个频率,从而降低系统的开销
- 弊端:池中线程或进程的数量是有上限的
# 单线程
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模块和爬虫之间的关联:
- 便捷地获取网站中动态加载的数据
- 便捷实现模拟登录
selenium模块:基于浏览器自动化的模块:通过编写一些python的相关代码,让这些python代码表示一些行为动作,让这些python代码所表示的行为动作触发到浏览器中,然后浏览器根据代码的指示完成相关的自动化操作(测试中也会使用selenium,如果大家感兴趣,可以自行百度)
selenium的使用流程:
- 环境的安装:pip install selenium
- 下载浏览器的驱动程序(谷歌浏览器的驱动程序)
- 驱动程序的路径:http://chromedriver.storage.googleapis.com/index.html
- 驱动程序和浏览器的映射关系:http://blog.csdn.net/huilan_same/article/details/51896672;或者直接在浏览器地址栏输入:chrome://version/
- 实例化一个浏览器对象
- 编写基于浏览器自动化的操作代码
火狐浏览器对应的驱动程序: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()
基于浏览器自动化的操作代码:
- 发起请求:get(url)
- 标签定位:find系列的方法
- 标签交互:send_keys(‘xxx’)
- 执行js程序execute_script(js代码)
- 前进、后退:back()、forward()
- 关闭浏览器: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)