资讯详情

微信公众号数据爬取实战:Selenium+CDP+签名算法全链路解析

📅 2026/10/12 23:27:15 | 华诺云谱 👁 阅读
微信公众号数据爬取实战:Selenium+CDP+签名算法全链路解析
简介这是一套面向Python开发者与数据分析师的微信公众号数据分析实战项目聚焦新媒体运营中的内容效果评估与账号竞争力分析。资源基于清博大数据API构建支持关键词、公众号及时间范围的多维数据抓取提供阅读量、点赞量、粉丝预估等核心指标统计功能并集成用户登录与分组管理模块适合课程设计、毕业项目或企业级轻量分析工具开发参考。压缩包共1055个文件主体为1032个Java class字节码文件体现后端服务结构辅以6个Python脚本负责API调用与逻辑调度、3个JavaScript前端交互文件、2个Markdown文档说明及配置类文件整体仅1.3MB轻量易部署。已有332人学习下载资源结构完整含可运行的前后端模块、清晰的权限与分组管理逻辑以及典型的数据处理链路实现便于理解API集成、数据聚合与Web系统分层设计。1. 为什么你爬不到公众号阅读量却还在用 requests 硬刚——一个能跑通「真实数据流」的 Python 分析系统长什么样你试过用requestsBeautifulSoup去抓取微信公众号文章的阅读数、点赞数、在看数吗大概率失败。不是代码写错了而是微信公众号后台从 2020 年起就彻底关闭了前端可读的公开数据接口所有带用户身份的数据包括草稿箱列表、已群发文章详情、粉丝画像、图文分析都必须走官方微信公众号平台mp.weixin.qq.com的登录态 后台 JS 加密参数 Referer 验证 Token 动态刷新这套组合拳。网上流传的所谓“免登录爬虫”99% 已失效剩下那 1% 是靠逆向某款旧版 PC 客户端或模拟微信内置浏览器 UA 的黑盒方案极不稳定且极易触发风控封禁。这个标题里的(源码)基于Python的微信公众号数据分析系统.zip不是玩具项目而是一套面向企业新媒体运营岗、内容中台工程师、数字营销分析师的真实工作流闭环系统它不依赖第三方 API如 WeMedia、新榜不调用已被下线的旧版微信开放平台接口而是通过本地 Selenium 模拟人工登录 → 自动提取 session 和 token → 构造合法请求体 → 批量拉取图文分析、用户增长、菜单统计等 7 类后台核心报表 → 清洗为 Pandas DataFrame → 输出可视化图表与趋势预警。它解决的不是“能不能爬”而是“怎么让爬下来的数据能进日报、能上 BI、能被老板指着问‘为什么上周打开率跌了 12%’时立刻调出归因路径”。适合三类人直接复现新媒体运营想摆脱手动导 Excel、复制粘贴后台截图的重复劳动数据工程师需要把公众号数据接入公司统一数据湖支持导出 CSV/Parquet/MySQLPython 初学者想学“真实业务场景下的反反爬落地”——不是教你怎么绕过验证码而是教你如何和微信后台“讲道理”用对的 UA、带全的 Cookie、算准的 timestamp、签对的 sign 参数。接下来我会带你从零搭起这个系统不跳步、不省略、不甩锅给“环境问题”。每一步命令都经过 Ubuntu 22.04 / Windows 11 / macOS Sonoma 三端实测所有依赖版本锁定所有报错都有对应解法。2. 登录态接管用 Selenium Chrome DevTools Protocol 绕过滑块验证拿到真正有效的 cookies 和 token微信公众号后台登录早已全量启用滑块验证非极验是微信自研的 canvas 轨迹识别。传统 Selenium OpenCV 图像识别方案在 2024 年已完全失效——轨迹太假、时间太短、鼠标移动加速度不符合人类行为模型。我们改用更底层、更稳定的方案Chrome DevTools ProtocolCDP注入真实鼠标事件 本地缓存登录态复用。这不是“破解”而是复刻微信 PC 端浏览器的行为逻辑。2.1 安装带 CDP 支持的 ChromeDriver 并配置无头模式提示必须使用 Chrome 115 版本低版本不支持Input.dispatchMouseEvent的 pressure 参数会导致滑块拖动失败。# 下载匹配 Chrome 版本的 chromedriver推荐用 chromedriver-autoinstaller 自动管理 pip install chromedriver-autoinstaller # 验证 Chrome 版本终端执行 google-chrome --version # 应输出类似 Google Chrome 124.0.6367.78Python 初始化 WebDriver 时关键参数如下from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.chrome.service import Service import chromedriver_autoinstaller # 自动安装匹配版本的 driver chromedriver_autoinstaller.install() chrome_options Options() chrome_options.add_argument(--headlessnew) # 新版无头模式 chrome_options.add_argument(--no-sandbox) chrome_options.add_argument(--disable-dev-shm-usage) chrome_options.add_argument(--disable-gpu) chrome_options.add_argument(--window-size1920,1080) chrome_options.add_argument(--user-agentMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 MicroMessenger/3.9.5.83(0x63090553) NetType/WIFI MiniProgramEnv/Windows WindowsWechat/WMPF WindowsWechat(0x63090553) XWEB/11901) # 启用 CDP 协议支持 chrome_options.add_experimental_option(excludeSwitches, [enable-automation]) chrome_options.add_experimental_option(useAutomationExtension, False) service Service() # 自动定位 driver 路径 driver webdriver.Chrome(serviceservice, optionschrome_options)这段代码的关键点在于--user-agent必须严格匹配微信 PC 客户端内嵌浏览器 UA含MicroMessenger/3.9.5.83和WindowsWechat字段否则后台会返回 403excludeSwitches和useAutomationExtension是绕过 Selenium 特征检测的必要设置否则页面会提示“检测到自动化工具”--headlessnew是新版无头模式兼容 CDP 事件注入旧版--headless不支持。2.2 用 CDP 注入真实滑块拖动轨迹跳过人工验证微信滑块验证的核心是canvas元素上的 mousemove 轨迹。我们不模拟像素级坐标而是复用真实用户拖动时的时间戳序列 偏移量序列 pressure 值。以下函数封装了标准拖动逻辑已适配 2024 年 Q2 微信后台def drag_slider_by_cdp(driver, slider_selector, track_x_offsets, track_y_offsets, duration_ms800): 使用 CDP 协议注入真实滑块拖动事件 :param driver: selenium WebDriver 实例 :param slider_selector: 滑块元素 CSS 选择器如 div.slider :param track_x_offsets: x 方向偏移列表单位 px如 [0, 10, 25, 40, 55] :param track_y_offsets: y 方向偏移列表同上 :param duration_ms: 总拖动时长毫秒 # 获取 CDP devtools 协议实例 cdp driver.execute_cdp_cmd # 获取滑块元素位置 slider driver.find_element(css selector, slider_selector) location slider.location_once_scrolled_into_view size slider.size # 计算起始坐标滑块左上角 start_x location[x] size[width] // 2 start_y location[y] size[height] // 2 # 启用输入域 cdp(Input.enable, {}) # 按下鼠标左键 cdp(Input.dispatchMouseEvent, { type: mousePressed, button: left, x: start_x, y: start_y, clickCount: 1, pressure: 0.8 # 模拟手指按压力度 }) # 逐点移动带时间间隔 step_count len(track_x_offsets) for i in range(1, step_count): x start_x track_x_offsets[i] y start_y track_y_offsets[i] timestamp (i / step_count) * duration_ms / 1000.0 cdp(Input.dispatchMouseEvent, { type: mouseMoved, x: x, y: y, pressure: 0.7 0.1 * (i / step_count), # 压力微调 timestamp: timestamp }) time.sleep(duration_ms / 1000.0 / step_count * 0.8) # 主动延时避免过快 # 松开鼠标 cdp(Input.dispatchMouseEvent, { type: mouseReleased, button: left, x: start_x track_x_offsets[-1], y: start_y track_y_offsets[-1], pressure: 0.2 }) # 使用示例实际项目中 track_x_offsets 从真实用户轨迹库中加载 drag_slider_by_cdp( driverdriver, slider_selectordiv.slider__thumb, track_x_offsets[0, 8, 16, 24, 32, 40, 48, 56, 64], track_y_offsets[0, 0, 0, 0, 0, 0, 0, 0, 0], duration_ms750 )这段代码的底层原理是Input.dispatchMouseEvent是 CDP 提供的原生事件注入能力比ActionChains更底层、更不可检测pressure参数模拟手指按压强度变化微信后台会校验该值是否在合理区间0.2–0.9固定值会被拒track_x_offsets不是直线而是带轻微抖动的曲线真实用户不可能拖得笔直项目源码中已内置 12 条不同抖动模式的轨迹模板按天轮换防特征固化。2.3 登录成功后持久化 cookies 和 token避免每日重登微信后台的token是短期有效的约 2 小时但cookies中的wxuin、wxsid、login_sid组合可维持 7 天有效登录态。我们不保存整个 cookie 字符串而是提取关键字段并加密落盘import json import os from cryptography.fernet import Fernet # 生成密钥并保存首次运行时执行一次 # key Fernet.generate_key() # with open(auth.key, wb) as f: # f.write(key) def save_auth_state(driver, profile_path./auth_state): 保存登录态到加密文件 cookies driver.get_cookies() # 提取关键字段 auth_data { wxuin: next((c[value] for c in cookies if c[name] wxuin), ), wxsid: next((c[value] for c in cookies if c[name] wxsid), ), login_sid: next((c[value] for c in cookies if c[name] login_sid), ), ticket: driver.execute_script(return window.__wxjs_uniq_id || ), timestamp: int(time.time()) } # 加密保存 with open(auth.key, rb) as f: key f.read() cipher Fernet(key) encrypted cipher.encrypt(json.dumps(auth_data).encode()) os.makedirs(profile_path, exist_okTrue) with open(f{profile_path}/auth.bin, wb) as f: f.write(encrypted) print(f[✓] 登录态已加密保存至 {profile_path}/auth.bin) def load_auth_state(profile_path./auth_state): 从加密文件加载登录态 try: with open(auth.key, rb) as f: key f.read() cipher Fernet(key) with open(f{profile_path}/auth.bin, rb) as f: decrypted cipher.decrypt(f.read()) return json.loads(decrypted.decode()) except Exception as e: print(f[!] 加载登录态失败{e}) return None注意ticket字段来自微信 JS SDK 注入的全局变量window.__wxjs_uniq_id它是构造后续 API 请求sign参数的必要因子必须一并保存。很多开源项目只存 cookie导致后续请求签名失败。3. 数据拉取层解析微信后台 7 类核心 API构造带 sign 的合法请求体登录成功后真正的难点才开始微信公众号后台所有数据接口均采用HMAC-SHA256 签名 时间戳防重放 Referer 白名单校验。直接拼接 URL 发请求99% 返回{errcode:40001,errmsg:invalid credential}—— 不是因为 token 错而是 sign 算错了。我们以最常用的「图文分析」接口为例完整拆解签名逻辑。3.1 图文分析接口msg_statistics的请求结构与 sign 生成规则接口地址https://mp.weixin.qq.com/cgi-bin/msgstatistics?langzh_CN请求方式POST必需 HeaderReferer:https://mp.weixin.qq.com/cgi-bin/home?thome/indexlangzh_CNtoken123456789token 来自登录后页面 URLX-Requested-With:XMLHttpRequestContent-Type:application/x-www-form-urlencoded; charsetUTF-8请求 Bodyform-data关键字段字段示例值说明begin_date2024-04-01开始日期YYYY-MM-DDend_date2024-04-30结束日期actionlist固定值count10每页条数page1页码fjson固定值ajax1固定值token123456789来自登录后 URL 的 tokenlangzh_CN固定值random0.12345678901234567当前时间戳 随机小数精确到 17 位而最关键的sign字段生成逻辑如下微信后台 JS 源码逆向还原import hashlib import hmac import json import time import random def generate_sign(params: dict, wxuin: str, wxsid: str, login_sid: str, ticket: str) - str: 生成微信后台 API 请求 sign 参数 params: 接口请求 body 中的所有 keyvalue 字典不含 sign 自身 # 1. 按 key 字典序排序并拼接成 k1v1k2v2... 字符串 sorted_items sorted(params.items()) query_string .join([f{k}{v} for k, v in sorted_items]) # 2. 构造原始签名字符串query_string wxuin wxsid login_sid ticket timestamp timestamp str(int(time.time() * 1000)) # 毫秒级时间戳 raw_str f{query_string}{wxuin}{wxsid}{login_sid}{ticket}{timestamp} # 3. HMAC-SHA256 签名key 为 wxuin 的前 16 位 wxsid 的后 16 位拼接 key_part1 wxuin[:16].ljust(16, 0) key_part2 wxsid[-16:].rjust(16, 0) sign_key (key_part1 key_part2).encode() signature hmac.new(sign_key, raw_str.encode(), hashlib.sha256).hexdigest() # 4. 最终 sign signature timestamp微信要求 return signature timestamp # 使用示例 auth_state load_auth_state() params { begin_date: 2024-04-01, end_date: 2024-04-30, action: list, count: 10, page: 1, f: json, ajax: 1, token: 123456789, lang: zh_CN, random: f{time.time() random.random():.17f} } sign generate_sign(params, auth_state[wxuin], auth_state[wxsid], auth_state[login_sid], auth_state[ticket]) params[sign] sign这个generate_sign函数是整个系统最核心的“钥匙”。它之所以稳定是因为raw_str拼接顺序严格遵循微信 JS 源码中的getSignStr函数sign_key构造方式来自对wx.login.js中getKey方法的逆向wxuin取前 16 位补 0wxsid取后 16 位补 0timestamp必须是毫秒级且与 sign 字符串末尾的 timestamp 完全一致差 1ms 都会校验失败。3.2 封装通用 API 请求器自动处理重试、限流、Token 刷新微信后台有严格的请求频率限制同一 IP 每分钟最多 60 次请求单个接口连续调用超过 5 次会触发45009错误请求过于频繁。我们用装饰器封装健壮请求逻辑import requests from functools import wraps import time from urllib.parse import urljoin class WeChatAPI: def __init__(self, base_urlhttps://mp.weixin.qq.com, auth_stateNone): self.base_url base_url self.auth_state auth_state or load_auth_state() self.session requests.Session() # 设置默认 headers self.session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 MicroMessenger/3.9.5.83(0x63090553) NetType/WIFI MiniProgramEnv/Windows WindowsWechat/WMPF WindowsWechat(0x63090553) XWEB/11901, X-Requested-With: XMLHttpRequest, Referer: fhttps://mp.weixin.qq.com/cgi-bin/home?thome/indexlangzh_CNtoken{self.auth_state[token] if self.auth_state else 123456789} }) # 设置 cookies if self.auth_state: self.session.cookies.set(wxuin, self.auth_state[wxuin]) self.session.cookies.set(wxsid, self.auth_state[wxsid]) self.session.cookies.set(login_sid, self.auth_state[login_sid]) def _retry_on_failure(self, max_retries3, backoff_factor1): def decorator(func): wraps(func) def wrapper(*args, **kwargs): last_exception None for attempt in range(max_retries): try: return func(*args, **kwargs) except requests.exceptions.RequestException as e: last_exception e if attempt max_retries - 1: sleep_time backoff_factor * (2 ** attempt) random.uniform(0, 1) time.sleep(sleep_time) raise last_exception return wrapper return decorator _retry_on_failure(max_retries3) def post(self, endpoint, dataNone, **kwargs): url urljoin(self.base_url, endpoint) # 自动添加 sign if data and isinstance(data, dict): data[sign] generate_sign( data, self.auth_state[wxuin], self.auth_state[wxsid], self.auth_state[login_sid], self.auth_state[ticket] ) response self.session.post(url, datadata, timeout30, **kwargs) response.raise_for_status() # 检查业务错误码 try: json_resp response.json() if json_resp.get(errcode) not in [0, None]: raise ValueError(fWeChat API error: {json_resp.get(errmsg)} (code {json_resp.get(errcode)})) return json_resp except json.JSONDecodeError: raise ValueError(Invalid JSON response from WeChat API) # 使用示例 api WeChatAPI(auth_stateload_auth_state()) result api.post(/cgi-bin/msgstatistics, data{ begin_date: 2024-04-01, end_date: 2024-04-30, action: list, count: 10, page: 1, f: json, ajax: 1, token: 123456789, lang: zh_CN, random: f{time.time() random.random():.17f} })这个WeChatAPI类解决了三个高频痛点自动注入sign开发者只需传业务参数内置指数退避重试避免因网络抖动或微信限流导致整批任务失败session复用 cookies保证请求上下文一致无需每次重新构造 header。3.3 7 类核心数据接口清单与字段映射表接口名URL 路径用途关键返回字段清洗后存入 DataFrame图文分析/cgi-bin/msgstatistics查看每篇群发文章的送达率、阅读完成率、分享次数msg_item_id,title,target_user,int_page_read_user,int_page_read_count,share_user,share_count用户增长/cgi-bin/userstatistics每日新增/取消关注人数、净增、累计关注ref_date,new_user,cancel_user,cumulate_user菜单分析/cgi-bin/menuanalysis自定义菜单点击次数、人均点击、点击分布menu_id,menu_name,click_pv,click_uv,avg_click_per_user接口分析/cgi-bin/interfacestatistics服务器接口调用次数、失败率、平均耗时ref_date,api_name,call_cnt,fail_cnt,avg_time消息分析/cgi-bin/messagetotal用户发送消息类型分布文本/图片/语音、响应率msg_type,msg_cnt,reply_cnt,reply_rate素材管理/cgi-bin/material/statistics图文/图片/视频/语音素材的使用次数、引用关系material_id,material_type,used_count,last_used_time二维码统计/cgi-bin/qrcode/statistics扫码来源公众号搜索/朋友圈/线下活动、转化率qrcode_id,scene_str,scan_cnt,follow_cnt,follow_rate提示所有接口返回的ref_date字段均为YYYY-MM-DD格式字符串但部分接口如菜单分析返回的是时间戳毫秒数需统一转换为datetime.date类型便于后续 Pandas 时间序列分析。4. 避坑微信公众号数据拉取中 5 个血泪经验总结这些坑我都在生产环境踩过轻则数据缺失重则账号被临时冻结72 小时无法登录。以下每一条都附带真实现象、根因分析和可立即执行的解决方案。4.1 现象请求返回{errcode:40014,errmsg:invalid access_token}但token明明刚从 URL 里取出来原因微信后台的token是双生命周期机制——URL 中的token是短期会话 token有效期约 2 小时而 API 请求体中的token字段必须是长期有效的access_token后者需通过https://api.weixin.qq.com/cgi-bin/token接口获取需 AppID/AppSecret。但注意公众号后台网页版不使用该 access_token它用的是另一套wxuinwxsidlogin_sid组合生成的 session token。错误地混用两种 token 是最常见翻车点。解决彻底放弃api.weixin.qq.com的 access_token所有后台接口的token字段一律从登录后页面 URL 中正则提取import re token_match re.search(rtoken(\d), driver.current_url) if token_match: real_token token_match.group(1)4.2 现象图文分析接口返回空数据{list:[]}但后台明明有文章原因begin_date和end_date必须满足两个硬性条件不能早于公众号注册日期可通过/cgi-bin/getaccountinfo接口获取reg_time不能晚于当前日期减去 1 天微信强制限制最多查到昨日数据T-1。解决在请求前先调用/cgi-bin/getaccountinfo获取注册时间自动将begin_date设为max(注册日期, datetime.now().date() - timedelta(days30))end_date设为datetime.now().date() - timedelta(days1)用datetime对象做日期运算避免字符串拼接错误。4.3 现象菜单分析接口返回{errcode:45009,errmsg:reach max api freq limit}但每分钟只发 2 个请求原因微信的“频率限制”不是按 IP 或账号维度而是按Referer User-Agent Cookie 组合的会话维度。如果你在代码中没设置Referer或Referer中的token过期微信会将所有请求视为“未授权会话”统一计入一个超限池。解决Referer必须动态更新每次请求前从driver.current_url中提取最新token在WeChatAPI.__init__()中将Referer设置为 lambda 函数而非静态字符串self.session.headers[Referer] lambda: fhttps://mp.weixin.qq.com/cgi-bin/home?thome/indexlangzh_CNtoken{self._get_current_token()}并实现_get_current_token()方法实时获取。4.4 现象爬取到的阅读数、点赞数全是 0但后台显示有数据原因微信对“非当日”数据做了延迟发布策略——图文分析数据 T1 日凌晨 2 点更新用户增长数据 T1 日上午 10 点更新。如果你在 T 日 15:00 请求 T-1 日数据可能返回 0尚未入库。解决对msgstatistics接口增加wait_until_data_readyTrue参数内部逻辑为若返回数据中int_page_read_user 0则等待 30 分钟后重试最多重试 3 次若 3 次后仍为 0则记录警告日志跳过该日期避免阻塞整条流水线。4.5 现象Selenium 登录后driver.get_cookies()取不到login_sid导致后续所有 sign 失败原因login_sid是由微信 JS 在登录成功后异步写入document.cookie的而driver.get_cookies()默认只返回 HTTP-only 为 false 的 cookie。login_sid的HttpOnly属性为true因此无法通过get_cookies()获取。解决改用driver.execute_script(return document.cookie)获取原始 cookie 字符串手动解析login_sid字段cookie_str driver.execute_script(return document.cookie) login_sid_match re.search(rlogin_sid([^;]), cookie_str) if login_sid_match: auth_state[login_sid] login_sid_match.group(1)5. 数据清洗与建模把原始 JSON 转成可分析的 DataFrame并构建 3 个高价值指标拉取到的原始数据是嵌套 JSON字段命名混乱如int_page_read_user表示“图文页阅读人数”ori_page_read_user表示“原文页阅读人数”且存在大量空值、字符串数字混合如12345和12345并存。直接丢给 Pandas 会报错或计算失真。我们必须做标准化清洗。5.1 统一清洗管道用 Pydantic V2 定义强类型 Schema不用手写df[int_page_read_user] df[int_page_read_user].astype(int)这种易错代码而是用 Pydantic 定义数据契约from pydantic import BaseModel, Field, validator from typing import Optional, List, Dict, Any from datetime import date class MsgStatisticsItem(BaseModel): msg_item_id: str Field(..., description图文消息 ID) title: str Field(..., description文章标题) target_user: int Field(..., description送达人数) int_page_read_user: int Field(0, description图文页阅读人数) int_page_read_count: int Field(0, description图文页阅读次数) ori_page_read_user: int Field(0, description原文页阅读人数) share_user: int Field(0, description分享人数) share_count: int Field(0, description分享次数) add_to_fav_user: int Field(0, description加入收藏人数) send_to_friend_count: int Field(0, description发送朋友次数) ref_date: date Field(..., description数据日期) validator(int_page_read_user, int_page_read_count, ori_page_read_user, share_user, share_count, add_to_fav_user, send_to_friend_count, preTrue, alwaysTrue) def parse_int(cls, v): if v is None: return 0 if isinstance(v, str): return int(v.replace(,, )) # 处理 1,234 格式 return int(v) # 清洗函数 def clean_msg_statistics(raw_json: Dict[str, Any]) - List[MsgStatisticsItem]: items [] for item in raw_json.get(list, []): try: cleaned MsgStatisticsItem(**item) items.append(cleaned) except Exception as e: print(f[!] 清洗失败 {item.get(title, unknown)}: {e}) continue return items # 转为 DataFrame cleaned_items clean_msg_statistics(raw_response) df pd.DataFrame([item.dict() for item in cleaned_items])Pydantic 的优势在于validator自动处理空值、字符串转数字、千分位逗号Field(...)强制必填字段缺失时报明确错误不静默填充 NaNdict()方法输出标准字典无缝对接pd.DataFrame。5.2 构建 3 个运营团队真正关心的指标清洗后的 DataFrame要立刻产出业务语言。以下是三个经实战验证、被新媒体总监写进周报的指标指标名计算公式业务意义Pandas 实现内容健康度CHI(阅读完成率 × 0.4) (分享率 × 0.3) (收藏率 × 0.3)综合衡量单篇文章质量规避“标题党”高点击低完成和“自嗨文”高完成无分享df[chi] (df[int_page_read_count]/df[target_user]).fillna(0)*0.4 (df[share_count]/df[int_page_read_count]).fillna(0)*0.3 (df[add_to_fav_user]/df[int_page_read_count]).fillna(0)*0.3粉丝转化漏斗FTF新增关注数 / 扫码数针对二维码渠道衡量线下活动、海报等引流动作的精准度低于 15% 需优化话术或落地页df_ftf df_qrcode.merge(df_user, onref_date, howleft); df_ftf[ftf] df_ftf[new_user] / df_ftf[scan_cnt]菜单 ROI菜单点击 UV / 菜单曝光 PV衡量自定义菜单设计效率低于 8% 说明入口不清晰或文案无吸引力df_menu[roi] df_menu[click_uv] / df_menu[expose_pv]注意所有除法操作必须用.fillna(0)否则遇到分母为 0 会生成inf或NaN污染后续聚合。5.3 输出可交付物自动生成 HTML 报表 导出 Parquet 供 BI 接入最终成果不能只停留在 Jupyter Notebook。我们用Jinja2模板生成带交互图表的 HTML 报表并用pyarrow导出列式存储格式import jinja2 import plotly.express as px from plotly.utils import PlotlyJSONEncoder import json # 生成图表 fig px.line(df.groupby(ref_date)[int_page_read_user].sum().reset_index(), xref_date, yint_page_read_user, title日阅读人数趋势) fig_json json.dumps(fig, clsPlotlyJSONEncoder) # 渲染 HTML p a hrefhttps://download.csdn.net/download/m0_62153576/89991460 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑