一个 Chrome 浏览器扩展(MV3),在个人所得税电子税务局 https://etax.chinatax.gov.cn/ 自动采集「申报查询」与「专项附加扣除信息查询」数据,导出完整 JSON 与合并 PDF 报表。跨页面刷新自动续采——这是它相对 bookmarklet 方案的核心优势。
⚠️ 本项目仅供学习研究。使用前请务必阅读文末《免责声明》。
电子税务局点「查看」进入详情页时页面会刷新,导致 bookmarklet 的 JS 上下文销毁、脚本中断。Chrome 扩展通过两层架构彻底解决:
| 层 | 职责 | 生命周期 |
|---|---|---|
| background(service worker) | 用 chrome.storage.local 存采集进度 |
持久(页面刷新不影响) |
| content script | 注入页面操作 DOM,每步把进度汇报给 background | 页面刷新后自动重新注入 |
页面刷新后,content script 重新注入 → 从 background 读回进度 → 根据当前页面判断该从哪步继续。用户无需干预,采集自动接着跑。
- 🛡️ 域名限定:仅在
etax.chinatax.gov.cn注入,其它站点无副作用。 - 📊 申报数据全套采集:
- 我要查询 → 申报查询
- 默认视图先抓:进页面不设日期条件,自带约 2 年数据,先抓这批(更快,也避开日期组件)
- 条件查询补采:默认视图采完后,把 税款所属期(month picker) 与 申报日期(date picker) 均设到「4 年前 1 月」(2026 → 2022-01)再查,与默认视图重复的年份跳过
- 查询 → 已完成 → 提取申报列表(去重)
- 逐条「查看」抓详情:基础信息 + 计税详情 + 工资薪金收入明细(翻页);点「查看」按申报项目+税款所属期文字匹配目标行(列表顺序乱也不点错),匹配失败回退行号
- 退税记录 tab:两项合计——
汇算已退税额1(退税金额合计,排除「税务审核不通过」)与已预缴税额(仅统计「国库处理完成」「税务审核中」,「已撤销退税申请」等一律不计;页面有「已预缴税额」列时优先取该列)(无该 tab 自动跳过) - 总收入 = 工资薪金收入 + 资薪奖金收入(计税详情表两行之和,JSON/PDF 申报概况均导出)
- 计税汇总「收入」口径:=「总收入明细」页头的 收入总额 + 单独计税奖金(如 298254.85+1330.94);页头未读到时保持计税详情汇总原值。计税详情表「工资薪金」行 = 页头「收入总额」 − 工资明细中「全年一次性奖金」行合计(收入总额含并入奖金,须剔除),
单独计税奖金(页头金额)单独入库,JSON/PDF 均导出 - 资薪奖金收入与类型(计税详情表「工资薪金」行下方自动插入,JSON/PDF 均导出
资薪奖金类型):无奖金 → 0、类型「无奖金」;奖金并入综合所得(单独计税=0,工资明细含「全年一次性奖金」行)→ 该类行收入合计、类型「奖金并入综合所得」,这些行即「单独计税奖金明细」;单独计税(页头.qnycxjj-money金额>0)→ 该金额、类型「奖金单独计税」,「单独计税奖金明细」合成一行(合计),且工资薪金明细剔除全年一次性奖金行(不重复计入工资)。奖金明细不再从页面点击抓取(曾误抓工资行) - 从详情页返回后自动重置查询条件,继续下一条
- 已作废 tab 补采:已完成逐条采完后自动切「已作废」tab,与已完成重复年份的行跳过,其余照常进详情(报表标注「已作废」)
- 今年(当前年)数据不采集:申报列表按年份过滤掉当前年,专项附加扣除年度止于上一年
- 收入纳税明细查询(申报查询全部阶段采完后衔接):我要查询 → 收入纳税明细查询。申报查询缺失的年份(如列表只有 2025/2024/2022,缺 2023)→ 翻页逐行「查看」读月度详情(本期收入/减除费用/专项扣除等),按月汇算生成该年年度数据(标注数据来源);已有申报数据的年份:当年无奖金自动跳过,有奖金则读「已申报税额合计」→
已预缴税额并翻页扫描明细合并 所得项目小类=「全年一次性奖金」行的「已申报税额(元)」→资薪奖金税额;任一步失败跳过不阻塞
- 🧾 专项附加扣除信息采集(申报完成后自动衔接):
- 我要查询 → 专项附加扣除信息查询
- 逐年导出(4 年前到上一年,如 2022→2025;今年不采):用 year picker 选扣除年度,选年即自动刷新
- 提取每条扣除卡片(项目 / 子女姓名·被赡养人·住房地址等键值 / 最后修改时间)
- 逐条「查看」抓明细:基本信息 / 教育信息 / 被赡养人信息 / 分摊方式 / 申报方式 / 设置扣除比例 等分段
- 多主体不丢(子女教育/3岁以下婴幼儿照护/赡养老人等):同页多个同名段(如两个孩子的「教育信息」)或段内多行,全部保留并按主体拆成逐条 item(报表/PDF 中多行展示);同(项目,年度)多次采集自动按内容去重合并
- 申报月数(子女教育/3岁以下婴幼儿照护/赡养老人条目附带):子女教育按「当前受教育阶段开始时间/结束时间」时间段与当年相交月数计算(如
2021-09-01/2025-07-31→ 2025 年报 7 个月;只有一端时按该月起/止);3岁以下婴幼儿照护按出生日期计算(出生月 ~ 满 3 周岁当月,如 2022-05 生 → 2022 年报 8 个月、2025 年报 5 个月);赡养老人按出生日期计算(满 60 周岁次月起算,如 1964-05 生 → 2024 年报 7 个月、其后每年 12 个月);随 JSON 与 PDF 导出 - 同项目同主体跨年名称相同 → 直接复用已采明细,不重复进明细页
- 👨👩👧 家庭成员采集(全部采集的最后阶段):右上头像 → 个人信息管理 → 家庭成员信息,逐卡片点「编辑」读 关系/姓名/出生日期(弹窗结构未校准,按通用 el-form-item 读取);专项附加扣除已查到的成员(子女/被赡养人)同名跳过、合并输出;任一步失败跳过不阻塞
- 🔒 导出脱敏:保存的 JSON / PDF(含内嵌 JSON 附件) / 历史缓存中,姓名(张三→张*)、身份证号(前6后4)、手机号(前3后4)、邮箱(本地部分仅留首字符)、门牌号均打码;合同编号保留前4后2(2009渝银房贷字第611803号→2009*3号);贷款银行只留前2字(中信银行→中信);公司与住址、就读学校只保留省级行政区(重庆市江北区…→重庆市;重庆石马河玉带山小学→重庆市)。例外:本人姓名在 PDF 中不脱敏——文件名与报表标题/「纳税人信息」均显示真实姓名(明文
realName仅存于历史记录,不进 payload 与内嵌 JSON 附件);家属姓名(配偶/子女/被赡养人/出租人)仍打码。 - 📤 导出:
- 采集完成后自动生成并下载 PDF:
{姓名}_个税报告_{年度数}_{采集时间}.pdf—— 把数据渲染为中文 HTML 报表后打印,阅读友好(文件名与报表内均为真实姓名;如张三_个税报告_4年_20260818_153012.pdf);含家庭成员独立一栏与页脚页码 - 完整 JSON 作为缓存写入历史(不下载文件),供「历史」tab 重新导出 PDF
- 采集完成后自动生成并下载 PDF:
- 🔄 跨刷新续采:点「查看」、面包屑返回等任何页面跳转后,自动从断点继续。
- 🪟 popup 控制台:点扩展图标显示状态/进度/实时日志。
采集逻辑(选择器、数据结构、日期组件操作)均来自同级
tax-tool(Python + Playwright)项目的真机校准结果。
- 打开 Chrome,地址栏输入
chrome://extensions。 - 右上角打开**「开发者模式」**开关。
- 点击**「加载已解压的扩展程序」**,选择本目录(
tax-export/)。 - 扩展出现在列表中,工具栏显示蓝色图标。
Edge 同样支持:打开
edge://extensions,开启开发者模式,加载本目录。
-
点击工具栏的扩展图标 → 弹出控制台。
-
点击**「开始采集」**。
-
脚本自动完成全流程;采集过程中页面会因导航/详情查看而刷新,扩展会自动续采,无需任何操作。
-
全部完成后,浏览器自动下载:
{姓名}_个税报告_{年度数}_{采集时间}.pdf—— 中文 HTML 报表打印的 PDF(如张三_个税报告_4年_20260818_153012.pdf,文件名为真实姓名)- 完整 JSON 作为缓存写入「历史」(不单独下载文件);在历史 tab 可重新导出 PDF
JSON 结构(缓存于历史,供 PDF 重新生成):
popup 关闭后再打开,会自动恢复显示当前进度与历史日志。若中途想重来,可在 chrome://extensions 里点扩展的「清除存储」或重载。
JSON → PDF 不依赖外部 PDF 库(避免中文字体嵌入问题),而是走 Chrome 自带打印引擎:
- 把 JSON payload 渲染成自包含、打印友好的中文 HTML 报表(
report.html+report.js)。 - background 用
chrome.windows.create({ state:'minimized' })隐藏打开该报表页。 - 报表就绪后通知 background;background 用
chrome.debugger(CDP)Page.printToPDF拿到 base64 PDF。 - 用 pdf-lib(
vendor/pdf-lib.min.js)把 payload JSON 作为 EmbeddedFile 附件写入 PDF(页面不显示、内容不变,仅 Adobe 等带附件面板的阅读器可见回形针)。 chrome.downloads下载 PDF。嵌入失败时自动降级为普通 PDF,不影响下载。
转换时会短暂出现最小化窗口 + 调试黄条(CDP 打印必需),属正常现象。中文使用系统中文字体(PingFang SC / Microsoft YaHei),无字体嵌入开销。
解析端无需从表格反解,直接读附件即可拿到原始完整 JSON(UTF-8):
# pip install pypdf
import json
from pypdf import PdfReader
payload = json.loads(PdfReader('税务数据报表_xxx.pdf').attachments['payload.json'][0].decode('utf-8'))其他工具:PDFBox doc.getAttachments();qpdf qpdf --show-attachment payload.json in.pdf out.json。
tax-export/
├── manifest.json # MV3 清单
├── background.js # service worker:存进度 + JSON 导出 + PDF(printToPDF) + 历史重导
├── report.html # JSON→HTML 报表渲染页(隐藏 minimized window 加载)
├── report.js # payload → 自包含中文 HTML 报表
├── vendor/
│ └── pdf-lib.min.js # pdf-lib(npm run vendor 从 node_modules 同步):PDF 内嵌 JSON 附件
├── content/ # 注入页面的脚本(按 manifest 顺序加载)
│ ├── utils.js # 底层工具 + JobStore + 日志适配层
│ ├── datepickers.js # 日期组件(month/date picker)
│ ├── list.js # 申报:列表准备 + 提取 + 点查看
│ ├── detail.js # 申报:详情采集(基础信息/计税/工资薪金)
│ ├── deduction.js # 专项附加扣除:年份选择 + 卡片提取 + 明细导出
│ └── main.js # 跨刷新状态机主编排
├── popup/ # 控制台 UI
│ ├── popup.html / .css / .js
└── icons/ # 16/48/128 图标
采集流程被拆成可持久化的状态,每步存盘,刷新后从断点继续:
【申报查询】(默认视图 → 条件查询 → 已作废,三段 衔接)
navigating → list → (点查看) → detail → (抓详情) → returning → (回列表) → list → ... → 默认视图遍历完
↑___________________________________________________|
current++ 后继续下一条
【条件查询补采】(默认视图遍历完后衔接)
cond → (设日期条件+查询) → 提取列表,排除默认视图已采年份 → 并入 rows → 继续逐条「查看」
【已作废 tab 补采】(条件查询遍历完后衔接)
voided → (重设条件+点已作废 tab) → 提取列表,排除已采年份 → 并入 rows → 继续逐条「查看」
【专项附加扣除】(申报完成后自动衔接)
deduction-navigating → deduction-year(逐年 setStartYear + 抽卡片)
→ deduction-detail(抓明细) → deduction-returning(面包屑回列表) → 下一条卡片
→ 卡片遍历完 → 下一年 → ... → 全部完成 → 导出
【导出】
申报 + 专项附加扣除 → 完整 JSON 下载 + 写入历史(可重导 PDF)
content script 注入时先读 background 的进度,根据 stage + 当前页面(申报列表/详情、专项附加扣除列表/明细)定位断点:
- 申报:
detail+ 在详情页 → 抓详情;returning+ 在列表页 → current++;list+ 在列表页 → 继续逐条 - 专项附加扣除:
deduction-detail+ 在明细页 → 抓明细;deduction-returning+ 在列表页 → 下一条卡片;deduction-year+ 在列表页 → 继续当年 - 其它 → 安全回退或从头开始
content/utils.js 顶部常量可调:
| 常量 | 默认 | 含义 |
|---|---|---|
YEAR_OFFSET |
-4 |
申报:目标年 = 当前年 + 偏移(2026 → 2022) |
MONTH |
1 |
申报:月份(1 = 一月) |
MAX_ROWS |
50 |
申报列表最多处理条数 |
MAX_PAGES |
50 |
工资薪金明细最多翻页数 |
DEDUCT_YEARS_OFFSET |
-4 |
专项附加扣除起始年 = 当前年 + 偏移,逐年抓到上一年(2026 → 2022..2025;今年不采) |
改完后在 chrome://extensions 点扩展的「刷新」按钮重新加载即可。
- 专项附加扣除任一步失败不阻塞:导航失败或无数据时,JSON 中该字段为
null,申报数据仍正常导出。 - JSON 导出用
chrome.downloads+ data URL;超大 JSON 理论上受 data URL 长度限制(实测常规申报数据远低于阈值)。 - PDF 导出依赖 CDP(
chrome.debugger),转换时浏览器顶栏会出现调试黄条(无法隐藏),属正常。 - 站点结构若改版,选择器需更新(content 日志会指出失败步骤,单条详情/明细失败不阻塞整体)。
- 需手动登录;扩展不触碰账号密码与加密层(与 tax-tool 合规口径一致)。
- 扩展仅在
etax.chinatax.gov.cn域名下运行,采集的全部数据只保存在你本机(chrome.storage.local),不上传任何服务器、不经过任何第三方。 - 导出的 JSON / PDF 含你的个人税务信息,请妥善保管,不要分享给他人或提交到公开仓库。
- 本仓库的
demo/tax_data.json与demo/output/(示例数据生成结果)已被.gitignore排除;若你本地生成的文件含真实个人信息,请勿提交到公开仓库。
使用本软件前请仔细阅读以下条款。下载、安装或使用本软件即表示你已阅读、理解并同意本声明的全部内容。 若不同意,请立即停止使用并删除本软件。
- 仅供个人学习与研究:本软件仅用于个人学习、技术研究,以及整理本人在个人所得税电子税务局的申报数据,供办理本人年度汇算清缴、退税时参考。
- 非官方工具:本软件与国家税务机关、国家税务总局及任何官方机构无任何关联,亦未获得任何官方授权或认可。本软件产生的数据不具备任何法律效力,办理税务事项请以电子税务局官方渠道的数据与口径为准。
- 信息真实性自担:因使用本软件采集、计算、导出的数据而进行的任何申报、更正、退税等操作,均属于使用者本人的自主行为;因数据误差、站点改版、软件缺陷或操作不当导致的任何后果(包括但不限于申报错误、退税延误、补税及滞纳金等),由使用者自行承担,作者不承担任何责任。
- 不构成税务建议:本软件(含
demo/中的退税计算模板)输出的任何金额、计算结果仅为参考,不构成任何税务、法律或财务建议。涉及重大税务决策请咨询专业人士或主管税务机关。 - 禁止滥用:严禁将本软件用于批量采集他人数据、爬取、攻击、干扰网站正常运行或其他任何违法违规用途;使用者须遵守《中华人民共和国网络安全法》《中华人民共和国个人信息保护法》及电子税务局的用户协议。
- 按「现状」提供:本软件按「现状」免费开源提供,不提供任何明示或默示的担保(包括但不限于适销性、特定用途适用性、不侵权的保证)。在法律允许的最大范围内,作者对因使用或无法使用本软件而产生的任何直接、间接、偶然、特殊或后果性损害概不负责。
- 条款可随时变更:本免责声明及软件功能可能随时更新而不另行通知,请以最新版本为准。
本项目基于 MIT License 开源发布。
{ "导出时间": "...", "申报": { "税款所属期起始": "...", "列表": [{ "申报项目":"2024年度综合所得年度汇算", "税款所属期":"...", "应缴税款(元)":"...", ... }], "详情": [{ "_detail_sections": { "基础信息": { "kv": {...}, "sections": {...} }, "计税详情": { "收入表": [{ "分类":"收入", "项目":"工资薪金", "金额(元)":"298254.85" }, ...], "汇总": { "收入":"...", "应纳税所得额":"...", "应纳税额":"...", "已缴税额":"..." }, "逐项金额": { ... }, "工资薪金明细": [{ "税款所属期":"2024-12", "收入(元)":"...", ... }] } } }] }, "专项附加扣除": { "扣除年度范围": "2022~2026", "记录": [{ "项目":"子女教育", "子女姓名":"...", "扣除年度":"2022年", ... }], "明细": [{ "sections": { "基本信息": {...}, "教育信息": {...}, ... }, "kv": {...}, "项目":"...", "扣除年度":"..." }] } }