鱼
retry模块
retry是一个用于错误处理的模块,功能类似try-except,但可以更加快捷方便的设置重试的次数,以及每次重试之间相隔的时间。
1 |
|
-
exceptions:传入指定的错误类型,默认为Exception,即捕获所有类型的错误,也可传入元组形式的多种指定错误类型。
-
tries:定义捕获错误之后重复运行次数,默认为-1,即为无数次。
-
delay:定义每次重复运行之间的停顿时长,单位秒,默认为0,即无停顿。
-
backoff:呈指数增长的每次重复运行之间的停顿时长,需要配合delay来使用,譬如delay设置为3,backoff设置为2,则第一次间隔为
3*2^0=3
秒,第二次3*2^1=6
秒,第三次3*2^2
=12秒,以此类推,默认为1。 -
max_delay:定义backoff和delay配合下出现的最大等待时间上限,当
delay*backoff**n
大于max_delay时,等待间隔固定为该值而不再增长。
项目结构
关于项目结构组织。由于Python在执行程序时,会自动将
.
即当前路径加入到库搜索路径,所以当下路径下的包(文件夹下有__init__.py
)都能被检测到使用,如下面的handler、helper都可以直接在其他文件里通过helper.xxx
来调用,但是顶层的settings.py
不能通过feiyu_shoot.settings
来调用,即使feiyu_shoot工程下也有__init__.py
,因为项目工程的上层目录并没有加入到Python环境搜索中,所以他实际不知道谁是feiyu_shoot
因此,根据这种设计,可以将项目结构组织成两种
-
不依赖子模块的放在root目录的top接口:需要被其他文件引用的列入文件夹中作为库使用,不需要被引用的可以直接放顶层
1
2
3
4
5# helper/runner.py
# handler/ 在.下作为包导入
from handler imoprt ConfigHandler
# settings.py 在.下作为模块导入
from settings import USER_AGENT -
★将入口放在最外层,核心内容作为单独一个文件夹(模块):由于feiyu是个整体的包,所以在feiyu下的任意Python文件中都可以通过
from feiyu.xxx import yyy
来导入。
根据Python打包利器:auto-py-to-exe中打包计算机程序的方式,更推荐第二种,这样代码资源文件位置更统一,也更加清晰一些。
more: [Python中模块、包、库定义](
pyinstaller
自我学Python以来推荐的就是这个,经过时光变迁,这个仍然是主流。
- PyInstaller是一个跨平台的Python应用打包工具,支持 Windows/Linux/MacOS三大主流平台,能够把 Python 脚本及其所在的 Python 解释器打包成可执行文件,从而允许最终用户在无需安装 Python 的情况下执行应用程序。
- PyInstaller 制作出来的执行文件并不是跨平台的,如果需要为不同平台打包,就要在相应平台上运行PyInstaller进行打包。
- PyInstaller打包的流程:读取编写好的Python项目–>分析其中条用的模块和库,并收集其文件副本(包括Python的解释器)–>将副本和Python项目文件(放在一个文件夹//封装在一个可执行文件)中。
安装就不细说了,主要讲用法:
-
通过命令行命令打包
pyinstaller -F main.py
,常用参数- -F: 表示生成单个可执行文件;对应的是-D: 生成文件夹形式的可执行程序(默认)
- -w: 表示去掉控制台窗口,这在GUI界面时非常有用。不过如果是命令行程序的话那就把这个选项删除吧!
- -p: 表示你自己自定义需要加载的库路径,一般情况下用不到
- -i: 表示可执行文件的图标
-
通过
.spec
打包定义文件来打包,对应命令行的参数,.spec
文件都会有对应的生成内容。首先根据main文件生成spec文件:
pyi-makespec -D main.py
、填写好后再pyinstaller main.spec
- -D是让spec中多一个coll的实例,从而变成文件夹
-i
相当于.spec
中EXE中icon=".\\debugs\\favicon.ico"
- …(更多spec文件参数选择见:https://blog.csdn.net/tangfreeze/article/details/112240342)
- -p: 相当于Analysis实例中的
pathex
,就是填入自己的模块 - –hiden-import: 相当于Analysis实例中的hiddenimports
实际上,根据命令行的参数会生成对应的.spec
文件。
注:可以看到无论是pyinstaller
、pyi-makespec
后面都是main.py
,因为其是main函数入口文件
附:根据feiyu,在此列两个可行的:
-
F:\aDevelopment\Python\ShowYourCode\env\Scripts\pyinstaller.exe main.py --add-data="feiyu\push_config.ini;.\feiyu" --add-data="feiyu\user_config.toml;.\feiyu" -i debugs\favicon.ico
-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35# -*- mode: python ; coding: utf-8 -*-
block_cipher = None # 此处在使用--key= 会有变化
a = Analysis(['ai\\main.py'],
pathex=['C:\\Users\\Admin\\Downloads\\marsai-master'],
binaries=[],
datas=[],# 此处可以添加静态资源,例如你有个图片文件夹imgs,可以这样写[('imgs','imgs'),('test.txt','.')],打包以后会有一个一样的文件夹,点表示当前文件夹。
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
[],
exclude_binaries=True,
name='main', # 生成的exe的名字
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True, # 打包的时候进行压缩,False表示不压缩
console=True # 是否显示黑窗口,刚开始打包的时候一般都会有问题,建议设为True,解决所有问题后可以设置为False)
coll = COLLECT(exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='main' # 文件夹的名字)
pyinstaller全都是控制台的命令,因此没那么直观,有人做了GUI相对直白、简单些,见Python打包利器:auto-py-to-exe,其关于计算器程序与Additional Files
的使用会让人对项目该如果布置结构和使用-p
参数有个更确信的认识(导入自己些的模块)。
dataclass
dataclasses嵌套
1 | def nested_dataclass(*args, **kwargs): |
**
解包得到的额外信息
1 | # 额外信息 |
pydantic.dataclasses.dataclass
是dataclasses.dataclass with validation
的替代品, 而不是pydantic.BaseModel
的替代品(在初始化挂钩的工作方式上有一点不同),在某些情况下,将pydanticis.BaseModel
子类化是更好的选择.
toml语法学习
TOML是前GitHub CEO, Tom Preston-Werner,于2013年创建的语言,其目标是成为一个小规模的易于使用的语义化配置文件格式.TOML是大小写敏感的,必须是UTF-8编码。
toml对象
字符串:跟python中一样,"""
、"
、'
、'''
注释:#
数组:数组使用方括号包裹。空格会被忽略,包括换行符。元素使用逗号分隔。
日期时间:toml中日期是一等公民,跟数组一样
键值对
TOML 文档的主要构建块
- 键可以是裸露的裸键,也可以是被包裹在一对
"
的内部的引用键。裸键只能存在字母、数字、下划线和破折号(a-z、A-Z、0-9、-
)。注意,裸键可以只由数字组成,例如1234
,但它总是被解析为字符串。引用键遵循与基本字符串或字面字符串完全相同的规范,并允许使用更广泛的键名称。最好的做法是使用裸键,除非必要情况。 - 值可以是以下类型:字符串、整数、浮点数、布尔值、时间、数组或内联表*。未确定的值是非法的。
- 注:键和值周围的空白符忽略不计,键、
=
和值必须位于同一行
表格
-
空表是允许的,只要里面没有键值对就行了。
-
表不能定义多于一次,不允许使用
[table]
头重定义这样的表。1
2
3
4
5
6
7# 不要这样做
[fruit]
apple = "红"
[fruit]
orange = "橙" -
同样地,使用点分隔键来重定义已经以
[table]
形式定义过的表也是不允许的。 -
不过,
[table]
形式可以被用来定义通过点分隔键定义的表中的子表。 -
不鼓励无序地定义表,即同级别子表建议放在一起定义
内联表:
内联表提供了一种更为紧凑的语法来表示表。
内联表被完整地定义在花括号之中:
{
和}
。 括号中,可以出现零或更多个以逗号分隔的键值对。
- 内联表得出现在同一行内。
- 内联表中,最后一对键值对后不允许终逗号(也称为尾逗号)。
- 不允许花括号中出现任何换行,除非在值中它们合法。
1 | name = { first = "Tom", last = "Preston-Werner" } |
实际上就是表的另一种紧凑写法,定义的还是表
表头的第一例定义了这个数组及其首个表元素,而后续的每个则在该数组中创建并定义一个新的表元素。
这些表按出现顺序插入该数组。首先是个数组;其次,数组中的元素为表内容。
1 | [[products]] |
库:
-
Python:toml
1
2
3
4
5
6import toml
class UserConfigHandler(metaclass=Singleton):
def __init__(self):
with open(USER_PARAM_FILE, 'r', encoding="utf8") as f:
self.r = toml.load(f) -
Go:
go get github.com/BurntSushi/toml
自用抢购尝试
1 |
|
base数据库
判断重定向
由于有个需求是,没有investor就不进行爬取,而是否有investor可以通过
https://xxxx.com/company_finance/investors
会不会进行重定向进行判断
那么如何判断是否发生重定向呢?两种
- 禁止
requests.get
的allow_redirect
属性
1. - 运行
allow_redirect
,然后通过重定向history查看
图片懒加载
图片懒加载是一种网页优化技术。图片作为一种网络资源,在被请求时也与普通静态资源一样,将占用网络资源,而一次性将整个页面的所有图片加载完,将大大增加页面的首屏加载时间。为了解决这种问题,通过前后端配合,使图片仅在浏览器当前视窗内出现时才加载该图片,达到减少首屏图片请求数的技术就被称为“图片懒加载”——优先加载网页结构,以及逻辑脚本,等重要内容全部加载完成之后再加载重要优先级低一些的图片资源(同时也比较耗网络资源),是一个从用户体验上进行分层的技术。
网站一般如何实现图片懒加载技术呢?
在网页源码中,在img标签中首先会使用一个“伪属性
”(通常使用src2
、origin
……)去存放真正的图片链接而并非是直接存放在src属性中。当图片出现到页面的可视化区域中,会动态将伪属性替换成src属性,完成图片的加载。
比如:站长素材案例后续分析:通过细致观察页面的结构后发现,网页中图片的链接是存储在了src2这个伪属性中。
因此针对爬取这类图片而言,可以
- 找到要爬取图片的位置(网页上的位置)
- 查看该网址的链接img_url
- 查看网页源代码,Ctrl+F后文本搜索该图片链接img_url
- 如果3没有则在开发者工具中Ctrl+F搜索带img_url的资源
- 如果是3,则通过找到的真正的xpath路径获取,或者re提取;如果是4则模仿接口去请求图片
本项目案例
pathlib库使用
-
获得当前脚本的绝对路径:
Path(__file__).resolve()
-
获得当前脚本所在目录的绝对路径:
Path(__file__).resolve().parent
-
根据路径创建文件夹:
LOG_PATH = Path(__file__).resolve().with("logs")
–>./logs/
,LOG_PATH.mkdir(parents=True, exist_ok=True)
,- parents:如果父目录不存在,是否创建父目录,即支持递归创建
- exist_ok:只有在目录不存在时创建目录,目录已存在时不会抛出异常。
-
获得当前路径下的所有文件(遍历文件夹):
[path for path in example_path.iterdir()]
-
返回目录中最后一个部分的扩展名:
Path('/Users/Anders/Documents/abc.gif').suffix
-
返回目录中多个扩展名列表:
Path('/Users/Anders/Documents/abc.tar.gz').suffixes
-
返回目录中最后一个部分的文件名:
Path('/Users/Anders/Documents/abc.gif').name
-
返回目录中最后一个部分的文件名(但是不包含后缀):
Path('/Users/Anders/Documents/abc.gif').stem
-
文件操作
1
2
3
4
5
6
7example_path = Path('/Users/Anders/Documents/information/JH.txt')
# 以open的形式
with example_path.open(encoding = 'GB2312') as f:
print(f.read())
# 或者直接用pathlib提供的获取内容的方式
example_path = Path('/Users/Anders/Documents/information/JH.txt')
example_path.read_text(encoding='GB2312')-
.read_text(): 以文本模式打开路径并并以字符串形式返回内容。
-
.read_bytes(): 以二进制/字节模式打开路径并以字节串的形式返回内容。
-
.write_text(): 打开路径并向其写入字符串数据。
1
2path = Path('data.json')
path.write_text('{"id": 123, "name": "James"}') -
.write_bytes(): 以二进制/字节模式打开路径并向其写入数据。
-
参考:https://blog.csdn.net/itanders/article/details/88754606
按住验证码
该网站对频率检测十分敏感,很快就会出现"按住"验证码,模拟按住倒不难,但是按完以后有时并不能解决验证…
但是意外发现,在“无痕模式”下进行重新登录后,就可以解决验证…但是通过requests进行模拟登录后cookies还是无效。
🌟记录一下这个问题,之后看看有没有时间弄清楚这个问题。
解决方案:
既然模拟不行,那么就手动,因此想用selenium来模拟点击登陆。但是因为俄罗斯的问题,无法登陆。
于是只好更换selenium为pyppeteer。之前就听说了pyppeteer是可以代替selenium的模拟神器,相比Selenium 不太方便的地方:比如速度太慢、对版本配置要求严苛,最麻烦是经常要更新对应的驱动,还有些网页是可以检测到是否使用了Selenium ,pyppeteer都有提升,这次也借机会学习一下。
安装:
pip install pyppeteer
, 需要Python3.6以上(因为用了async)
pyppeteer
最简单demo:打开网页,并截图
1 | import asyncio |
执行JS,相当于requests-html中的render
1 |
|
元素选择器:
Page.querySelector()
/Page.querySelectorAll()
/Page.xpath()
缩写为Page.J()
, Page.JJ()
, and Page.Jx()
.
xpath进阶:
- starts-with 顾名思义,匹配一个属性开始位置的关键字
- contains 匹配一个属性值中包含的字符串
text()
匹配的是显示文本信息://a[contains(text(), "百度搜索")]
元素操作:点击、输入
pyppeteer.input.Mouse
click(x: float*, y: float, options: dict = None*, ***kwargs)
button
(str):left
,right
, ormiddle
, defaults toleft
:- 右键点击:
await page.click('a#cccccc',{"button": "right"})
- 右键点击:
clickCount
(int): defaults to 1.delay
(int|float): Time to wait betweenmousedown
andmouseup
in milliseconds. Defaults to 0.
down
(options: dict = None, *kwargs) `button
(str):left
,right
, ormiddle
, defaults toleft
.clickCount
(int): defaults to 1.move(x: float, y: float, options: dict = None, **kwargs*)
p(options: dict = None, **kwargs*)
1 | # 使用xpath |
launch
-
headless
:无界面模式 -
devtools
:启用开发者助手 -
userDataDir='./userdata'
: 记录些用户信息,比如cookies -
dumpio=True
防止浏览器开多个页面而卡死 -
executablePath
: 指定自定义的chrome路径 -
参数args设置,可以类比selenium
-
--start-maximized
: 页面全屏 -
--window-size={width},{height}
:设置窗口大小,区别于页面的page.setViewport({'width': 1366, 'height': 768})
-
--disable-infobars
:不显示信息栏 比如 chrome正在受到自动测试软件的控制 -
--incognito
:百度搜到的chrome.exe无痕命令(selenium是如此,pyppeteer不推荐) -
--user-agent
: 比如'--user-agent=Mozilla/5.0'
-
--proxy-server={proxy}
: 浏览器代理 配合某些中间人代理使用 -
--no-sandbox
, # 取消沙盒模式 沙盒模式下权限太小 -
'--log-level=3'
-
'--load-extension={}'.format(chrome_extension)
、'--disable-extensions-except={}'.format(chrome_extension)
# 加载插件1
2
3
4
5chrome_extension_path = "插件所在目录"
args = [
"--load-extension={}".format(chrome_extension_path),
"--disable-extensions-except={}".format(chrome_extension_path),
]
-
-
more: launch参数说明
执行JS:
-
page.evaluate('window.scrollBy(100, document.body.scrollHeight)')
-
await page.evaluate('alert("在浏览器执行js脚本!")')
1
2
3# 相当于拿到了dom对象
element = await page.J('#ul>a[name="tj_trtieba"]')
print(await page.evaluate('el => el.innerHTML', element)) -
page.evaluateOnNewDocument(pageFunction[, …args]), 指定的函数在所属的页面被创建并且所属页面的任意script执行之前被调用。常用于修改页面JS环境。
1
2
3
4page = await browser.newPage()
await page.setViewPort({'width': 1366, 'height': 768})
await page.evaluateOnNewDocument('''() => {
Object.defineProperty(navigator, 'webdriver', {get: () => false });附—ElementHandle元素操作:
- 获取元素边界框坐标:boundingBox(),返回元素的边界框(相对于主框架)=> x 坐标、 y 坐标、width、height
- 元素是否可见:isIntersectingViewport()
- 上传文件:uploadFile(*filpaths)
- ElementHandle 类 转 Frame类:contentFrame(),如果句柄未引用iframe,则返回None。
- 聚焦该元素:focus()
- 与鼠标相关:hover () ,将鼠标悬停到元素上面
- 与键盘相关:press (key[, options]),按键,key 表示按键的名称,option可配置:
- text (string) - 如果指定,则使用此文本生成输入事件
- delay (number) - keydown 和 keyup 之间等待的时间。默认是 0
page.keyboard.down('Shift')
:按下shift
more: 页面元素操作
安全性:
- 在使用 Pyppeteer 仍然会遇到无头浏览器检测,这里安利一个第三方库「pyppeteer-stealth」,「puppeteer-extra-plugin-stealth」引用Github上的说明「Applies various evasion techniques to make detection of headless puppeteer harder.」「A plugin for puppeteer-extra to prevent detection.」(Github地址:https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra-plugin-stealth )。可见,「pyppeteer-stealth」也是用于防止机器人检测的。用法也很简单,给page加上stealth
1 | import asyncio |
这样就可以省去隐藏WebDriver等操作,可谓省时省力省心。
await page.evaluateOnNewDocument('Object.defineProperty(navigator,"webdriver",{get:()=>undefined})')
stealth.min.js
最完美方案!模拟浏览器如何正确隐藏特征
无痕隐身模式访问
1 | async def start_browser(): |
较全的API说明:pyppeteer的基本使用、页面信息、设置
问题:按钮不生效
网页输入账号和密码之前,按钮状态为disable=true
,两个都输入后才能按下,而使用pyppeteer时,发现无论怎么尝试都不能使得其disable变化(用开发者助手修改后,无法生成正确的提交请求)。突然巧合之中,想到了是不是元素没加载成功,于是确实在开发者助手的console
中看到有很多不支持的,比如angular、bootstrap……
于是百度问题,看看是不是什么设置没开,但都没找到。后来想到升级下pyppeteer版本会不会好一点,发现自己的版本为1.0.2,已经是最新的了(https://github.com/miyakogi/pyppeteer/issues/306)。由于不通过程序直接通过浏览器进入程序也出现了无法点击的问题。再加上下面有人提出chromium有点难下载,并通过源码找道了默认的配置和设置,于是经过一阵的折腾过后,我发现**“按钮无法点击”的问题就在于chromium版本过低**。
1 | import pyppeteer.chromium_downloader |
登录这个storage的网站可以看到,上面所记录的chromium全都是2013年的版本,必然版本落后了。于是下载最新的chromium覆盖C:\Users\mrli\AppData\Local\pyppeteer\pyppeteer\local-chromium\588429\chrome-win32
目录后再运行,按钮不能点击的问题就消失了!~😄
启发于:使用pyppeteer 下载chromium 报错 或速度慢、pyppeteer使用时常见的bug及解决办法【转载】
- 📖pyppeteer实战案例:Pyppeteer的使用——爬取京东——消除指纹、页面滑动事件、获取数据,获取到的是Json数据:
(await a[0].getProperty("textContent")).jsonValue()
- 📖官方API: https://pyppeteer.github.io/pyppeteer/reference.html#pyppeteer.page.Page.xpath、puppeteer中文手册
- setViewport全屏
zsxq
操作word——docx
安装:
pip install python-docx
- 插入图片
1 | doc = Document() |
- 插入节
1 | add_heading(text=u'', level=1) # 写入标题段落 |
- 添加页眉
1 | document = Document() |
注:“页眉也是一个块级对象,里面也包含了 Paragraph 对象,” “所以对齐方式,文字格式设置方式和前文介绍一致。”
1 | head_par = head.paragraphs[0] |
- 修改页脚
1 | font0 = sec0.footer # 返回页脚对象 |
- 添加表格
1 | table1 = doc.add_table(2,3,style ='Table Grid' ) |
注: 单元格内其实也是有 paragraph 对象的,即对单元格内字体设置方式,和对段落中文字格式设置方法一样。
1 | cell_par = cell_new.paragraphs[0] # 获取到对象 |
- 段落设置
1 | # 设置段落缩进 |
更多操作样式见:
- ★Python操纵Word神器——python-docx大全(含插入pptx图表)
- Python-docx 读写 Word 文档:插入图片、表格,设置表格样式,章节,页眉页脚等——表格、页眉页脚
- python-docx处理word文档——段落操作(字体属性可选项)
Save后操作Document对象再save会覆盖
Document对象在save之后,对其操作仍然有效,后续的操作在新的save中会保留上次save的记录,然后再添加新的(实则是覆盖)
1 | d = Document() |
编辑已存在的word文档
python-docx 不仅可以创建word文档,还可以编辑已存在的word文档。
1 | from docx import Document |
但其实吧,这玩意儿只能编辑已存在的word文档,之所以有个“创建空白文档”的功能,只不过是拷贝一份空白word文档到工作区间,再在空白文档上编辑,看起来似乎是“创建空白文档”罢了。本质上还是编辑已存在的word文档…
打开一个word文档,编辑完后,一定要记得保存。如果保存文件名和原文件名不一样,则会另存为一份word文档;若文件名一样,则会不加提示的保存修改内容。
__slots__
作用
用于需要维护两个Saver, 因此中间根据返回值来获得了枚举确定saver对象, 但是saver对象有个saver_name的属性并没有绑定起来, 因此想到
通过setattr
来添加属性
1 | class A: |
但不幸的是Document中也无法通过setattr
添加属性, 原因应该是Document类也定义了__slots__
。
在此, 也明确下__slots__
的特点:
__slots__
数据类型为元组__slots__
只对当前类生效,对其子类不生效
BytesIO使用
说起IO,很多人首先想到的是磁盘中的文件,将磁盘中的文件读到内存以及内存内容写入文件,显然这种文件与内存的File-IO是种非常典型的IO。然而其实还存在一种内存和内存之间的IO,叫类文件对象(file-like object,在内存中创建,可以像文件一样被操作)。Python原生提供两个类文件对象:StringIO和BytesIO。
Q问题一:为什么要有内存级别的IO?
A:磁盘上的文件,就是将数据持久化到磁盘的一块区域,供后面重复使用。其优点就是持久化稳定不丢失,但是缺点也很明显,就是每次要使用都要从磁盘读入,相对内存而言很缓慢;如果只是短时间的重复利用,并不希望长期持久化,而且对速度的要求比较高,这时候就可以考虑缓存。说到缓存,很多朋友就想到redis,熟悉python的朋友还会想到装饰器和闭包函数。不过python已经原生为我们准备好了类文件对象:StringIO和BytesIO,用于临时缓冲
Q问题二:StringIO和BytesIO区别?
A:StringIO只能存储字符串,遇到从网络下载的图片视频等Bytes类型的内容就不行了,需要用到专门存储Bytes类型的BytesIO对象。
1 | In [1]: from io import BytesIO |
Q问题三:那么这两个IO有什么作用呢?
A: BytesIO真正实用的地方还是在于存储图片视频等数据,比如网络请求了一个图片,拿到了resp.content
的字节流,然后需要打开展示图片,但①pillow库打开的都是文件IO对象,无法直接通过字节流展示;②保存到本地作为文件打开又没必要,既占用存储空间,效率又低–>此时就可以写入到内存中的IO(BytesIO)中
Q问题四:那么StringIO和BytesIO跟普通用的f = open()
的FileIO有什么相同点呢
A:StringIO和BytesIO仍然为IO,但拥有和读写文件具有一致的接口,其本质是将内存作为一个文件看待,只是在内存中操作str和bytes。
本项目案例:
python-docx
库中Document对象可以通过add_picture()
方法插入图片到word中,但是add_picture
的参数为img_path
或者stream
,那么这个Stream流指的肯定是图片的字节流,但是直接通过bytes数据肯定是没办法传入的,因此可以将其写入到BytesIO中,供其读取
1 | d = Document() |
注:通过bf.flush()无法清空内容, 必须close(), 否则bf.write(新图片)
然后再添加时第二张图片内容仍然为第一张。
★ --> 网上没能找到清除BytesIO已有内容,因此结论为:一个bytes内容占用一个BytesIO,用完就关闭
loguru
loguru中提供全局的logger, 可以直接通过
from loguru import logger
来获得 logger, 但是这就意味着, 这样获得的都是都一个logger
对象, 因此不太方便用于分模块的日志管理, 而是全局的日志管理。
loguru的便捷体现在其开箱即用(少配置)的特点, 下面两行代码就能输出美观的日志输出
1 | from loguru import logger |
其次, loguru配置日志文件也非常方便, 他提出属于为**“沉量”**sink
logger.add(sink='log.txt', format="{time} {level} {message}", filter="my_module", level="INFO")
将上面两个功能合起来,就能实现最基本的日志功能了。
1 | from loguru import logger |
可以用rotation、retention、compression进行日志窗口、更新、压缩管理。
1 | logger.add("file_1.log", rotation="500 MB") # 日志文件的窗口大小是500M |
more:
- 修改日志文字的颜色
logger.add(sys.stdout, colorize=True, format="<green>{time}</green> <level>{message}</level>")
- 使用enqueue,可以保证多线程安全、多进程安全
logger.add("somefile.log", enqueue=True)
参考: Loguru:优雅的Python程序日志
本项目自用loguru配置
settings.py
中找到项目绝对路径:basedir = Path(__file__).resolve().parent.parent
- 将loguru中的logger注册为全局LOG, 暴露给其他模块使用
1 | import time |
retry模块
▲本项目中不知为何原因,options+get之后总会出现
{'succeeded': False, 'code': 1059, 'info': '', 'resp_data': {}}
的数据,但是进行重试发送请求后又能解决,因此retry模块在这个项目中发挥中较大的作用。
retry是一个用于错误处理的模块,功能类似try-except,但可以更加快捷方便的设置重试的次数,以及每次重试之间相隔的时间。
1 |
|
-
exceptions:传入指定的错误类型,默认为Exception,即捕获所有类型的错误,也可传入元组形式的多种指定错误类型。
-
tries:定义捕获错误之后重复运行次数,默认为-1,即为无数次。
-
delay:定义每次重复运行之间的停顿时长,单位秒,默认为0,即无停顿。
-
backoff:呈指数增长的每次重复运行之间的停顿时长,需要配合delay来使用,譬如delay设置为3,backoff设置为2,则第一次间隔为
3*2^0=3
秒,第二次3*2^1=6
秒,第三次3*2^2
=12秒,以此类推,默认为1。 -
max_delay:定义backoff和delay配合下出现的最大等待时间上限,当
delay*backoff**n
大于max_delay时,等待间隔固定为该值而不再增长。
qkl
requests-html
安装:
pip install requests-html
,只支持python3.6及以上
由于其用了nuxt
框架,因此有些路由数据保存在windows.__NUXT__.data
中,而这些数据都是变量,无法直接通过正则提取出来,但是因为其可以在控制台输出已经渲染生成好的windows.__NUXT__.data
数据,因此考虑到用chromium后执行渲染脚本应该可以实现。但之前有了解过requets-html,其相对于要开浏览器而言更加便捷一些,于是本次使用的是requests-html。
快速上手:
由于与requests库出于同一作者,因此API和requests库
都差不多,主要的区别在于resp.html
这个属性,它是整个requests_html库
中最核心的一个类,负责对HTML进行解析。比如分页、渲染等功能都是由该对象提供的。
相比requests的新功能有:
- 支持JavaScript
- 支持页面解析:CSS选择器(又名jQuery风格, 感谢PyQuery)、支持Xpath选择器
- 可自定义模拟User-Agent(模拟得更像真正的web浏览器)
- 自动追踪重定向
- 连接池与cookie持久化
- 支持异步请求
- 智能多页
获取链接
links和absolute_links两个属性分别返回HTML对象(URL)所包含的所有链接和绝对链接(均不包含锚点)(已经自动去掉了html标签)。
1 | # 获取响应页面下的所有链接 |
获取元素
request-html支持CSS选择器(借助PyQuery)和XPATH(借助lxml)两种语法来选取HTML元素,我一般用xpath比较多,因此就介绍xpath了,selector的用法是类似的。
XPAT语法,需要另一个函数xpath的支持,它有4个参数:
- selector,要用的XPATH选择器;
- clean,布尔值,如果为真会忽略HTML中style和script标签造成的影响(原文是sanitize,大概这么理解);
- first,布尔值,如果为真会返回第一个元素,否则会返回满足条件的元素列表;
_encoding
,编码格式。
1 | print(r.html.xpath("//div[@id='menu']", first=True).text) |
元素内容
要搜索元素的文本内容,用search函数
1 | print(e.search('还是{}没头脑')[0]) |
两个链接属性
1 | print(e.links) # 相对路径 |
JavaScript支持
有些网站是使用JavaScript渲染的,直接爬取页面结构或者是接口获得数据可能不是想要的数据,这样的网站requests-html也可以处理JS渲染。关键一步就是在HTML结果上调用一下render函数,它会在用户目录(默认是~/.pyppeteer/)中下载一个chromium,然后用它来执行JS代码。
1 | h = session.get('http://python-requests.org/') |
render函数还有一些参数:
- retries: 加载页面失败的次数
- script: 页面上需要执行的JS脚本(可选)
- wait: 加载页面前的等待时间(秒),防止超时(可选)
- scrolldown: 页面向下滚动的次数
- sleep: 在页面初次渲染之后的等待时间
- reload: 如果为假,那么页面不会从浏览器中加载,而是从内存中加载
- keep_page: 如果为真,允许你用r.html.page访问页面
智能分页
1 | rq = session.get("https://reddit.com") |
直接使用HTML
1 | from requests_html |
直接渲染JS代码
1 | script = """ |
r.html.page与浏览器交互
requests-html将对chromium的键盘和鼠标API操作聚合在了
r.html.page
对象下
键盘事件
- keyboard.down(‘键盘名称’):按下键盘不弹起(与键盘有点不太down(‘h’)只会出现一个h而不是hhhhhhh…)
- keyboard.up(‘键盘名称’):抬起按键
- keyboard.press(‘键盘名称’):按下+弹起
- keyboard.type(‘输入的字符串内容’,{‘delay’:100}) delay为每个子输入后延迟时间单位为
ms
鼠标事件
点击
- click(‘css选择器’,{ ‘button’:‘left’, ‘clickCount’:1,‘delay’:0})
- button为鼠标的按键
left
,right
, ormiddle
, - clickCount:点击次数默认次数为1
- delay:点击延迟时间,单位是毫秒
- button为鼠标的按键
- mouse.click(x, y,{ ‘button’:‘left’, ‘clickCount’:1,‘delay’:0})
- x,y:muber数据类型,代表点击对象的坐标
点下去不抬起
- mouse.down({‘button’:xxx,clickCount:xxx})
抬起鼠标
- mouse.up({‘button’:xxx,clickCount:xxx})
等待
- waitFor(‘选择器, 方法 或者 超时时间’)
- 选择器: css 选择器或者一个xpath 根据是不是
//
开头 - 方法:时候此方法是page.waitForFunction()的简写
- 超时时间:单位毫秒
- 选择器: css 选择器或者一个xpath 根据是不是
等待元素加载: waitForSelector('css选择器')
获取x,y坐标
1 | mydic =await r.html.page.evaluate('''() =>{ |
📖
- python自学爬虫之requests-html——整体API介绍
- requests-html库render方法的使用 ——与浏览器交互操作
request-html异步
1 | from requests_html import AsyncHTMLSession |
and:https://cloud.tencent.com/developer/article/1575104
- 📖asession.run无法传参的问题——修改requests_html.AsyncHTMLSessions使得支持url参数
charles+VPN无法共用——解决方案
由于两个都是代理型的软件,因此往往默认都指定了一个端口,导致另一个失效,所以需要通过设置让他们端口互知。
最近需要抓一个需要翻墙才能访问的网页的包,发现VPN直连时会导致 Fiddler 和 Charles 抓包工具无法正常进行抓包,网上找了以后发现了一些解决方案:Github:VPN直连,导致 Fiddler 和 Charles 抓包工具无法正常进行抓包解决方案 ——试了貌似没用、windows下,实现vpn访问下的charles抓包设置中无网络问题的解决——收此启发指导了在charles的Proxy->external proxy
允许其他端口代理
1.找到VPN软件的代理端口proxy port
我这边使用的是vmess,可以在选项->参数设置
中查看,需要明确的参数是端口和协议,我这边是10808和socks协议
2.设置charles:
Proxy->external proxy
, 首先允许其他proxy,然后根据刚刚查看到的vmess端口和协议进行填写
3.设置完成,开始抓包
完结撒花~
requests使用代理
1 | import requests |
注:一开始在Sublime里运行的,结果一直在response.text
时报编码错误,但是通过网页的content-type和meta charset进行确认过没问题,后来经过一个启发想到会不会是控制台有编码显示不了,于是在Pycharm中运行,成功!
aiohttp使用socks代理
from: https://pypi.org/project/aiohttp-socks/、https://www.cnblogs.com/john-xiong/p/13812567.html
-
pip install aiohttp_socks
-
1
2
3
4
5
6
7
8
9connector = ProxyConnector.from_url('socks5://127.0.0.1:10808')
async def getDataByChromeDriver(url):
async with aiohttp.ClientSession(connector=connector) as session:
async with session.get(url) as response:
return await response.text()
if __name__ == '__main__':
loop.run_until_complete(asyncio.wait([getDataByChromeDriver(index) for title, index in title_list.items()])) -
运行即可
request-html使用代理
1 | from requests_html import AsyncHTMLSession |
Author: Mrli
Link: https://nymrli.top/2022/05/02/2022年5月劳动节爬虫项目记录/
Copyright: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.