七麦APP数据爬虫实战:从API采集到CSV归档与zip打包

七麦APP数据爬虫实战:从API采集到CSV归档与zip打包 简介七麦APP数据爬虫是一份基于Scrapy框架的Python数据采集项目主要面向爬虫初学者、数据采集人员与移动应用分析爱好者。该资源以七麦APP为实际抓取对象完整演示了爬虫工作流程中的关键环节从初始URL收集与队列构建到利用Requests库请求页面再通过XPath或Beautiful Soup解析HTML提取应用榜单、下载量、评分等目标信息最后将结果写入结构化存储同时针对网站常见防护提供了robots.txt协议遵守、请求频率限制、User-Agent伪装及反爬应对的工程实现。压缩包共11个文件包含9个Python脚本、1个Markdown说明文档和1个Scrapy配置文件整体仅20KB轻量紧凑。Python脚本按爬虫模块分离便于阅读和二次开发README.md概述项目逻辑scrapy.cfg定义运行环境目录结构对标Scrapy标准工程适合移植到其他站点抓取任务。目前已有331人学习下载对想快速搭建移动端数据采集工具的开发者来说是一份具备参考价值的入门范本。1. 破解 App Store 数据视图七麦APP数据爬虫.zip 在解决什么问题七麦数据是移动互联网市场情报平台覆盖 App Store、Android 应用市场的榜单、关键词、评论、下载量预估。七麦APP数据爬虫.zip 解压后通常是抓取这些数据的 Python 脚本外加配置文件。用它可以把“排名趋势”“竞品关键词覆盖”变成每天自动更新的本地 CSV再按周打成 zip 归档。相比人肉打开网页一个个记这个爬虫的价值是把重复劳动交给定时任务。适用人群是 ASO 从业者、App 产品经理和做竞品分析的研发。理解它需要三块页面与接口关系、请求参数含义、归档打包策略。下面从页面结构讲起。2. 先认清页面和接口七麦APP数据爬虫的定位逻辑2.1 七麦数据页面上“看起来”和“实际上”的数据路径七麦数据的几个常用页面排行榜总榜、免费榜、付费榜、App 详情、关键词优化、热搜榜。排行榜页面展示表格App 详情页展示历史趋势。如果直接解析 HTML会碰到大量动态渲染和 token 校验得不偿失。常见做法是打开浏览器开发者工具切到 Network 面板刷新榜单页过滤 XHR 或 Fetch会看到返回 JSON 的接口。这些接口才是爬虫真正的数据源。以榜单页为例典型请求路径形如https://api.qimai.cn/rank/index查询参数里常见genre应用分类、country国家地区、date榜单日期、page页码。响应 JSON 里一般有rank_list或data数组每条记录包含app_id、app_name、app_icon、rank、revenue等字段。字段名会随版本变化所以爬虫里最好把字段映射单独放一个字典而不是写死在取值代码里。这样接口改版时只需要更新一个映射表采集主流程不动。2.2 用开发者工具确认接口和参数我一般会手动浏览一次目标页面然后执行筛选步骤打开七麦数据排行榜页并登录账号。按 F12 进入开发者工具选择 Network 面板。清空记录后点击下一页或切换分类。在 Network 面板里找到名称含index或rank的 XHR 请求。查看 Payload 或 Query String Parameters确认page、size等参数格式。在 Preview 里展开 JSON找到需要的字段名。这一步不需要写代码但能决定爬虫的准确性。很多爬虫跑不通是因为把page传成page_index或者把country传成country_code。七麦不同接口的命名并不统一以当前网络响应为准。把接口地址和参数含义记录到项目里的endpoints.md中后面调试时能省很多时间。2.3 最小可跑通的 requests 请求确定接口和参数后先写一个最小请求验证能不能拿到数据import requests url https://api.qimai.cn/rank/index params { genre: 36, # 36 代表工具类可换成其他分类 ID country: cn, # 中国大陆区 date: 2025-01-01, # 榜单日期 page: 1, size: 20 } headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64), Referer: https://www.qimai.cn/rank } resp requests.get(url, paramsparams, headersheaders, timeout10) print(resp.status_code) print(resp.text[:500])这段代码的作用是验证接口连通性。params里的genre是 App Store 分类编号country是国家代码date决定查哪天的榜单page和size一起控制分页。如果返回的状态码不是 200先检查登录 Cookie如果返回 JSON 里 code 不为 0说明缺少签名参数或频率过快。输出前 500 字是为了快速确认返回结构不要在一开始就把整段 JSON 打到终端。确认没问题后再扩大采集范围。提示直接在公开接口上高频请求很容易被限流。先等 1 秒再跑下一个请求。若发现请求头里缺少必要字段用浏览器复制为 cURL再转成 requests 代码。3. 完善采集逻辑七麦APP数据爬虫的字段、分页与限速3.1 明确要存的字段七麦能提供的数据很多但爬虫不要什么都存。以“竞品排行监控”为例我一般保留以下字段字段名示例值说明date2025-01-01榜单日期rank3实时排名app_id123456789App Store App 唯一标识app_name微信App 名称category社交分类名称sellerTencent开发者名称rating4.9评分rating_count10240评论数download_estimate12345下载量预估值字段名要从实际接口响应里映射。比如七麦返回的可能是appInfo.name嵌套结构就需要用item[appInfo][app_name]提取。字段映射放字典后续改版时只改一处。如果接口额外返回了很多不需要的字段不要试图全部存下来那样会让 CSV 变得难以阅读也会拖慢写入速度。3.2 分页参数怎么传排行榜接口的page从 1 开始size一般最大支持 50。采集超过 50 条时循环翻页import time import requests def fetch_rank(url, headers, genre, date, total_pages3): rows [] for page in range(1, total_pages 1): params { genre: genre, country: cn, date: date, page: page, size: 50, # 每页条数超过接口上限会被拒绝 } try: resp requests.get(url, paramsparams, headersheaders, timeout15) resp.raise_for_status() data resp.json() # 常见的返回格式data 下面还有 rank_list rank_list data.get(data, {}).get(rank_list) or [] rows.extend(rank_list) except Exception as exc: print(fpage {page} failed: {exc}) time.sleep(1) # 保守限速避免 429 return rows分页的主要陷阱有两个一是部分接口第一页从 1 开始也有从 0 开始的要观察真实请求二是返回总数可能没有total字段只能在rank_list为空时停止翻页。代码中data.get(data, {}).get(rank_list)是双重取值避免某个层级缺失时抛异常。限速放在请求结束之后而不是请求开始之前能降低并发窗口。如果你发现翻页到某页后数据重复出现说明page起始值写错了改成page - 1重新测试。3.3 重试与超时不要让爬虫死在第 3 页网络请求不稳定不加重试的爬虫会在第 3 页失败后整体中断。推荐用requests自带的适配器设置重试而不是在循环里写三个tryfrom requests.adapters import HTTPAdapter session requests.Session() adapter HTTPAdapter(max_retries3, pool_connections10, pool_maxsize10) session.mount(https://, adapter) session.mount(http://, adapter) for page in range(1, 6): try: resp session.get(url, paramsparams, headersheaders, timeout10) print(page, resp.status_code) except requests.exceptions.RetryError: print(page, retry exhausted) time.sleep(2)HTTPAdapter(max_retries3)会把连接失败、读超时等场景自动重试 3 次。pool_connections和pool_maxsize控制连接池大小对多线程采集有用。注意默认重试不包含状态码 429 或 5xx 的响应只处理网络层错误。如果你想让 429 也触发重试需要自定义Retry对象把status_forcelist加入 429、500、502。具体写法在最后实战部分给出。4. 采集结果打包成 zipCSV 归档与多文件写入4.1 写 CSV 之前先解决编码采集到的数据最终要给人看。直接用csv.writer写出来的 UTF-8 文件用 Excel 打开会乱码。常见做法是写入带 BOM 的 UTF-8也就是在文件开头写\ufeff。这样 Windows 上的 Excel 能正确识别。import csv def write_csv(rows, path): with open(path, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnamesrows[0].keys()) writer.writeheader() writer.writerows(rows)encodingutf-8-sig就是带 BOM 的 UTF-8。newline是 csv 模块配合要求避免在 Windows 下产生多余空行。DictWriter的fieldnames直接用第一行的键省去手写表头代价是要求所有行的字段顺序一致。如果个别行缺少字段使用extrasactionignore或先做字段补全。这里最好在调用write_csv前确认rows不是空列表否则rows[0]会抛异常。4.2 按日期生成 zip 文件爬虫每天跑一次生成rank_2025-01-01.csv。为了避免文件堆满目录可以按周打包成一个 zip。这里要用到zipfile注意写入文件名不要带绝对路径否则解压时会带出目录结构。import zipfile from pathlib import Path def pack_csv_files(csv_paths, zip_path): with zipfile.ZipFile(zip_path, w, zipfile.ZIP_DEFLATED) as zf: for csv_path in csv_paths: p Path(csv_path) zf.write(csv_path, arcnamep.name) # 只保留文件名ZIP_DEFLATED表示使用压缩算法CSV 文本压缩率很高。arcnamep.name是把源文件的全路径截成纯文件名否则 zip 内部会出现多级目录。多人协作时最好在压缩前检查是否存在同名文件可以加一个文件去重逻辑。另外如果 csv 文件当天正在被写入先确认文件已经关闭再打包否则 zip 里会出现大小为 0 的空文件。4.3 zip 写入失败常见原因写入 zip 时最大的坑不是压缩逻辑而是文件被占用或路径不存在。常见错误信息 “could not find central directory” 或 “EOCD not found” 多数出现在解压阶段也就是压缩包本身没写完。判断方法unzip -t rank_2025-01-01.zip如果unzip -t报错多半是程序在ZipFile.close()之前被中断或者磁盘满了。爬虫代码里用with语句能保证close()执行但还是建议在打包完成后检查文件大小小于 1KB 的 zip 基本可以视为失败。另一个容易被忽略的问题是 zip 内文件名编码。如果 CSV 文件名包含中文某些老版本解压工具会乱码最稳妥的方式是把文件名统一成rank_{date}.csv的英文格式。这样既兼容旧工具也方便脚本按日期范围批量处理。5. 反爬应对和增量采集让七麦APP数据爬虫稳定跑得更久5.1 Cookie 与签名参数的处理思路七麦的接口在未登录状态下返回的数据有限登录后 cookie 里会带上auth之类的凭证。爬虫里把 cookie 保存到本地文件每次请求前读取可以减少重复登录。但只带 cookie 还不够接口可能校验请求签名。常见做法是复制浏览器请求头里的synct或analysis参数观察它们的生成规律。不要一上来就逆向 JS先用“复制现有参数 定期人工刷新”的方式跑通流程再考虑用自动化工具生成签名。签名参数一般包含时间戳和固定盐值抓包后可以用execjs执行对应的 JS 文件来生成但前提是拿到真实的 JS 源码。注意签名方案随时可能升级。把签名生成逻辑抽成一个独立函数后续改版只替换函数内部实现爬虫主体流程不受影响。5.2 增量采集不要每天重爬全量日更榜单其实只需要存储变化的部分。把前一天的 CSV 读入内存以app_id为键构建一个集合今天抓到的新数据只要app_id不在集合里就认为是新增排名变化可以通过比较rank字段得到。增量表设计成如下结构CREATE TABLE app_rank_daily ( date TEXT NOT NULL, app_id INTEGER NOT NULL, rank INTEGER, changed INTEGER DEFAULT 1, PRIMARY KEY (date, app_id) );使用 SQLite 的好处是省去 CSV 的读取开销还能用 SQL 直接算排名变化。对于七麦爬虫这种每天几千条的数据量SQLite 完全够用。写入时用INSERT OR REPLACE同一个date app_id只保留最新一条。想保留历史快照就只追加一个snapshot_ts字段永远不同步删除旧记录。增量采集跑完后再把每天结果导出到 CSV 并打入 zip 归档这样既保证查询速度又不丢失原始数据。5.3 结果自校用抽样核对排名打包 zip 之后还要验证数据不是“空跑”。我一般会随机抽 5 个app_id重新访问七麦详情页人工核对排名和评分。也可以在采集日志里输出断言def validate_rows(rows, expect_min50): if len(rows) expect_min: raise ValueError(frows less than expect: {len(rows)}) # 排名应该连续且不重复 ranks [row[rank] for row in rows] assert len(set(ranks)) len(ranks), duplicated rank这个校验逻辑放在写 CSV 之前。expect_min根据榜单页大小设定总榜 Top 50 就设 50如果返回的行数异常少应该停止本次归档并通知人工检查。rank 重复说明某页数据没翻到或字段读取错位常见原因是把免费榜和付费榜混在一起存了同一 App 在不同榜的 rank 会重复。校验规则别追求复杂两条就够数量够、排名不重复。5.4 定时任务与过期清理爬虫稳定后用系统级的cron每天凌晨执行一次。命令示例0 2 * * * cd /opt/qimai /usr/bin/python3 crawler.py logs/qimai.log 21这里0 2 * * *表示每天凌晨 2 点运行。日志重定向到单独文件方便排查夜里失败的任务。同时每周清理一次超过 30 天的原始 CSV保留 zip 归档即可。清理脚本配合find做自定义保留策略不要手动删除避免误删当天正在写入的文件。最后再提一个技巧zip 打包完成后把zipfile.Path读出来统计里面 CSV 的行数写入一个manifest.json。下次验证时先对比 manifest 里的行数和实际解压后的行数不一致就说明归档被截断。这比光看文件大小可靠许多。本文还有配套的精品资源点击获取