sailor0913

须知少时凌云志,曾许人间第一流

Pyqt5+playwright实战记录和踩坑分享

前情提要

几年前一朋友对我说他工作中有时候需要把工作网站上的很多新闻内容转换为pdf,他的方法就是打开每一篇新闻---ctrl+p---另存为PDF。如果要转换的数量少还说,如果要转换的数量多,那就是一件很痛苦的事情了。于是他希望我能帮他写一个小工具解决这个痛点。

当时的工作网站并没有使用前后端分离,所有网页都是直接渲染,我爬取了所有网页后直接用pdfkit就很方便的转换成了pdf,就写了一个shell工具,然后给他用了。前段时间他又找到了我,说不知道什么原因,那个工具不好使了。我打开了网站,发现网站已经使用了前后端分离,所有的网页都是通过ajax请求获取数据,然后渲染的。这就尴尬了,我之前的爬虫工具就不能用了。

新的网站本身并没有做什么反爬,只需要构造正常的请求就可以拿到数据,但是朋友的需求是要把所有内容外加网站的样式一起转换成pdf。这个时候pdfkit就不能满足需求了。

再查了资料后发现无头浏览器可以满足这个需求,于是用playwright试了下,完全可以满足需求。为了让朋友更方便的使用,我就用pyqt5写了个小工具,当然第一次接触playwright和pyqt5,踩了不少坑,特此记录方便他人参考。

特别说明

因为是对特定网站的爬取,所以代码不方便开源,还请谅解

正式开始

playwright基础使用

安装&初始化
pip install playwright
playwright install # 务必记得初始化
最简demo
# 不使用context
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
	browser = p.chromium.launch(headless=False)
	page = browser.new_page()
	page.goto("https://www.baidu.com")
	page.wait_for_timeout(3000)
	browser.close()

# 使用context
from playwright.sync_api import sync_playwright

def test(playwright):
	browser = playwright.chromium.launch(headless=False)
	context = browser.new_context()

	page = context.new_page()
	page.goto("https://www.baidu.com")
	page.wait_for_timeout(3000)
	page.close()
	browser.close()

with sync_playwright as playwright:
	test(playwright)

# 异步
import asyncio
from playwright.async_api import async_playwright

async def run() -> None:
    async with async_playwright() as p:
        browser = await p.chromium.launch()
        page = await browser.new_page()
        await page.goto("http://www.baidu.com")
        await page.screenshot(path="example.png")
        await page.wait_for_timeout(3000)
        await browser.close()

asyncio.run(run())

技巧

录屏
playwright codegen -o test.py -b chromium
反爬
page = context.new_page()
page.add_init_script(path="stealth.min.js")
打包
# 下面两行必须有,把playwright相关环境打包
set PLAYWRIGHT_BROWSERS_PATH=0
playwright install chromium
# 使用pyinstaller打包
pyinstaller -F  xxxyyy.py

最后生成的PDF文字内容不全(文字已经加载完毕)
width, height = 1366, 768

browser = await p.chromium.launch(args=[
           f"--window-size={width},{height}",
        ],
            headless=False
        )

await page.set_viewport_size({"width": width, "height": height})

await page.pdf(path="playwright123.pdf", width="1366", height="768")
最后生成的PDF图片不全
Timeout 30000.0ms exceeded while waiting for event “popup” & waiting for event popup

Pyqt5基础

安装
pip install PyQt5 -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install pyqt5-tools -i https://pypi.tuna.tsinghua.edu.cn/simple

# 实测安装下面这个才可以调用designer
pip install pyqt5designer -i https://pypi.tuna.tsinghua.edu.cn/simple
配置

技巧

高分辨率下解决界面错乱
from PyQt5 import QtCore

QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True)

使用了子线程后程序在debug模式下正常,但是运行会闪退
# 部分代码
class Ui_MainWindow(object):
    def __init__(self):
        super().__init__()
        # 加不加不影响最终结果
        # self.thread = None

		def test_thread(self, login_user):
			from child_thread_functions.sync_task_to_pdf import ThreadTask
			print("test_thread")
			thread = ThreadTask()
			thread.success.connect(self.test)
			thread.start()

        # 正确答案
        # self.thread = ThreadTask()
        # self.thread.success.connect(self.test)
        # self.thread.start()

		def test(self):
		  print("test")

		def connect(self):
			self.button.clicked.connect(self.test_thread)

class ThreadTask(QThread):
    success = pyqtSignal(int)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def run(self):
        print("进入子线程run")
        # self.with_cookie_sync_task()
        self.success.emit(1)