前言

安装playwright

pip install -i <https://pypi.tuna.tsinghua.edu.cn/simple> pytest-playwright
python -m playwright install

录制页面交互

playwright codegen --save-storage cookie.json <https://ltc.xxx.com.cn/> --viewport-size 1920,1080

Untitled

填写模板配置

; config.ini
; Sample configuration file
[DEFAULT]
; chromium驱动位置
executable_path=""
; 钉钉Token
token=""
; 新建类型
type=日常工作
; 工作类型
work_type=服务工具研究与开发
; 工作内容类型
work_content_type=天穹综合服务工具
; 工作方式
way_of_working=公司
; 工作内容
work_content=要填写的日报
from configparser import ConfigParser
import os
import codecs
def read_config():
    config = {
        "token": "",
        "type": "日常工作",
        "work_type": "服务工具研究与开发",
        "work_content_type": "天穹综合服务工具",
        "way_of_working": "公司",
        "work_content": "要填写的日报",
        "executable_path": "",
    }
    cfg = ConfigParser()
    if os.path.isfile('config.ini'):
        cfg.read_file(codecs.open("config.ini", "r", "utf-8-sig"))  
        for k, v in config.items():
            config[k] = cfg["DEFAULT"].get(k, v)
    return config

解析钉钉登录二维码

pip install -i <https://pypi.tuna.tsinghua.edu.cn/simple> opencv-python opencv-contrib-python
import cv2
import base64
import requests
import json
def send_qrcode(page):
    qrcode = page.frame_locator("iframe").frame_locator(
        "iframe").get_by_role("img", name="Scan me!")
    qrcode_decode = qrcode.get_attribute("src")
    qrcode_decode = qrcode_decode.removeprefix("data:image/png;base64,")
    with open("ding.png", "wb") as ding:
        ding.write(base64.b64decode(qrcode_decode))
    src = cv2.imread("ding.png", cv2.IMREAD_IGNORE_ORIENTATION)
    decoder = cv2.wechat_qrcode.WeChatQRCode()
    codeinfo, points = decoder.detectAndDecode(src)
    webhook = "<https://oapi.dingtalk.com/robot/send?access_token=>" + \\
        CONFIG.get("token")
    payload = {"msgtype": "link", "link": {
        "title": "登录二维码" + os.getlogin(), "text": "点击链接登录平台", "messageUrl": codeinfo[0]}}
    resp = requests.post(url=webhook, data=json.dumps(payload), headers={
                         "Content-Type": "application/json;charset=utf-8"})
    print("发送到钉钉,请点击登录")

Untitled

Untitled


自动填写

def run(playwright: Playwright) -> None:
    executable_path = CONFIG.get("executable_path")
    if os.path.isfile(executable_path):
        browser = playwright.chromium.launch(
            executable_path=executable_path, headless=False)
    else:
        browser = playwright.chromium.launch(headless=False)
    # 读取保存的缓存,如果没过期可以跳过登录,应该有三天有效期
    if os.path.isfile("cookie.json"):
        context = browser.new_context(storage_state="cookie.json", viewport={
                                      'width': root_ndim_x / 1, 'height': root_ndim_y / 1 - 200}, ignore_https_errors=True)
    else:
        context = browser.new_context(viewport={
                                      'width': root_ndim_x / 1, 'height': root_ndim_y / 1}, ignore_https_errors=True)
    page = context.new_page()
    info = get_info(context.storage_state())
    if info.get("t__expires__"):
        # 如果会话超时重新登录
        expires = datetime.fromtimestamp(int(info.get("t__expires__")) / 1000)
        if expires <= datetime.now():
            page.goto("<https://ltc.xxx.com.cn/stand-alone-login.html>")
            send_qrcode(page=page)
        else:
            page.goto("<https://ltc.xxx.com.cn/#/app/home>")
            # 如果会话超时重新登录
            if page.url == "<https://ltc.xxx.com.cn/stand-alone-login.html>":
                send_qrcode(page=page)
    else:
        page.goto("<https://ltc.xxx.com.cn/stand-alone-login.html>")
        send_qrcode(page=page)
    # 如果有引导操作点击跳过
    if "todo-guide-flag" not in str(info):
        page.get_by_role("button", name="跳过").click()
    # 等待页面加载完成
    page.locator("a").filter(has_text="工时管理").click()
    # 登录成功,保存会话到本地缓存
    context.storage_state(path="cookie.json")
    page.locator("a").filter(has_text="工作日报").first.click()
    page.frame_locator("#view").get_by_role("button", name="提报").click()
    page.frame_locator("#view").get_by_role("button", name="+添加工作记录").click()
    page.frame_locator("#view").get_by_label("新建类型").click()
    page.frame_locator("#view").get_by_title(CONFIG."type"), exact=True).get_by_text(CONFIG.get("type")).click()
    page.frame_locator("#view").locator("#typeWork").click()
    if CONFIG.get("work_type") == "日常工作":
        page.frame_locator("#view").get_by_text(CONFIG.get("work_type"), exact=True).nth(4).click()
    else:
        page.frame_locator("#view").get_by_text(CONFIG.get("work_type")).click()
    page.frame_locator("#view").locator("#jobType").click()
    page.frame_locator("#view").get_by_text(CONFIG.get("work_content_type")).click()
    page.frame_locator("#view").get_by_label("工作方式").click()
    page.frame_locator("#view").get_by_title(CONFIG.get("way_of_working")).get_by_text(CONFIG.get("way_of_working")).click()
    page.frame_locator("#view").get_by_label("工作工时(H)").click()
    page.frame_locator("#view").get_by_label("工作工时(H)").fill("8")
    page.frame_locator("#view").get_by_label("工作内容").click()
    page.frame_locator("#view").get_by_label("工作内容").fill(CONFIG.get("work_content"))
    # # ---------------------
    # 暂停页面,手动点击提交
    page.pause()
    context.close()
    browser.close()

with sync_playwright() as playwright:
    run(playwright)

使用nuitka打包成单文件

--include-package-data=PACKAGE                                                                                                                 
                        Include data files for the given package name. DLLs                                                                        
                        and extension modules are not data files and never                                                                         
                        included like this. Can use patterns the filenames as                                                                      
                        indicated below. Data files of packages are not                                                                            
                        included by default, but package configuration can do                                                                      
                        it. This will only include non-DLL, non-extension                                                                          
                        modules, i.e. actual data files. After a ":"                                                                               
                        optionally a filename pattern can be given as well,                                                                        
                        selecting only matching files. Examples: "--include-
                        package-data=package_name" (all files) "--include-
                        package-data=package_name=*.txt" (only certain type) "
                        --include-package-data=package_name=some_filename.dat
                        (concrete file) Default empty.
python -m nuitka --onefile --show-memory --show-progress --follow-import-to=pytest-playwright,opencv-python,opencv-contrib-python,asyncio,socket,playwright --enable-plugin=tk-inter --include-package-data=playwright --follow-stdlib --output-dir=out --windows-icon-from-ico=./favicon.ico main.py

Untitled

效果

ltc.png

参考

Powered by Kali-Team