ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python爬虫实战:无账号抓取企查查企业数据,详解接口分析与反爬应对

Python爬虫实战:无账号抓取企查查企业数据,详解接口分析与反爬应对 在爬虫圈里混了几年经常有人问我“企查查能不能爬”“怎么绕过去”。说实话这类企业信息查询平台的数据价值很高但反爬也在不断升级。今天分享的这套思路是我在实际项目里反复验证过的一种方式不需要账号不碰付费接口纯靠分析网页端请求就能拿到一批企业基础数据。文章会拆解请求头、接口参数、数据解析这几个核心环节最后附上完整代码和踩坑记录。如果你是刚接触爬虫想找一个真实商业平台练手或者需要批量采集企业公开信息做数据分析这篇可以帮你省掉不少弯路。不过我先把话放在前面所有技术仅供学习接口分析用抓取时务必控制频率、遵守目标网站的规则别拿去做违反平台协议的事。1. 项目背景与合规边界1.1 为什么选择企查查作为目标企查查这类平台聚合了工商登记、司法风险、知识产权、舆情信息等大量公开数据。对做市场调研、风险控制、数据建模的人来说这些信息是刚需。但从技术角度看它也是一个很典型的爬虫练习场数据量大、数据格式稳定JSON居多、反爬机制层次分明。我选择它作为案例是因为它完美覆盖了爬虫开发中最常见的问题——动态加载、请求参数加密、Cookie维持、频率限制。把这一套流程啃下来再去面对其他同类型平台基本都能触类旁通。这里要特别强调“公开”两个字。本文只讨论无需登录状态下通过分析网页前端代码能看到的、服务器直接返回给浏览器的数据。不涉及撞库、越权、破解验证码等非法操作。合规的边界很简单你模拟的是浏览器的正常行为访问的是用户不登录也能看到的页面拿到的数据量和你在浏览器里手动翻页看到的一样。只要不把请求频率打到让服务器过载不批量下载后用于商业售卖学习用途是没问题的。1.2 技术选型requests lxml为什么不用 Scrapy爬虫框架从 requests 到大而全的 Scrapy再到异步的 aiohttp各有各的适用场景。单就“快速验证一个接口能不能通”这个需求来说Scrapy 的组件和中间件反而显得笨重。我用 requests 加 lxml 的组合原因是足够轻量依赖少装完就能跑。而且我们可以先用 requests 把整个流程跑通确认数据源稳定后面真到了需要大规模采集的时候再无缝换成 Scrapy 也只是多写几个 pipeline 的事。另一个考虑是调试的直观性。requests 的响应对象能直接返回状态码、头部、编码我可以很快判断是请求头问题还是页面问题。配合 headers 里带上浏览器标识基本能做到和真实浏览器一样的请求效果。如果你正处于入门阶段我更推荐先把这个库用得滚瓜烂熟再去研究框架的调度机制。毕竟底层逻辑都是 HTTP 请求的构造与响应解析基础打牢了框架只是帮你省写重复代码而已。2. 企查查网页端请求流程拆解2.1 从搜索框到数据返回中间发生了什么在浏览器里打开企查查首页不登录直接在搜索框输入“腾讯科技”按下回车。这时候观察开发者工具的 Network 面板会发现请求不是一次性全部返回的。页面先加载了一个搜索建议的接口返回一批企业名称的联想词然后当你点击某个具体企业或者页面自动跳转到搜索结果列表时才会请求另一个列表接口再点进详情页又是一轮新的请求。所以所谓“无账号拿数据”核心就是找到这些不需要登录就能访问的接口。打开浏览器开发者工具切到“网络”标签页把“所有”类型的请求都刷出来。搜索关键词后在请求列表里找那些返回 JSON 格式文件的请求。通常它们的 URL 里会带search、list、query之类的单词响应预览里能看到企业名称列表。我们就把这类接口作为切入点。这里有个小技巧在开发者工具的“网络”面板里开启“保留日志”功能然后一边操作页面一边观察新产生的请求。很多关键接口在初次加载时就被调用但因为页面跳转导致日志被清空所以记得勾选这个选项。我当初排查时就是因为漏了这一下愣是多花了半天才找到真正的数据接口。2.2 请求头的关键字段User-Agent、Referer、Cookie找到接口后先别急着用代码请求。右键该请求选择“复制 - 以 cURL 格式复制”这是最稳妥的方式。为什么因为 cURL 格式里包含了浏览器自动附带的所有请求头尤其重要的是User-Agent、Referer、Accept-Language这几个字段。User-Agent用来标识客户端类型。如果是一个纯 Python 的默认 UA服务器很容易识别出来并触发反爬。复制浏览器 UA 是最基础的一步。Referer表示请求来源页面。很多服务器的安全策略会校验这个字段来源不对直接拒绝。企查查的接口就特别吃这一套不带正确的 Referer返回的往往不是你想要的 JSON而是个登录跳转页面。Cookie不登录也会有一批自动生成的 Cookie比如设备标识、访问时间戳。这些相当于匿名凭证告诉服务器“我是一个正常的浏览器会话”。实际操作中我会把这些请求头整理成一个字典写死在代码里然后用requests.Session()来维持会话。Session 会自动保存服务器返回的 Cookie并在后续请求中带上这个特性对应对需要“二次验证”的场景很有用。举个例子第一次请求搜索接口时服务器下发了一个token类的 Cookie第二次请求列表接口时需要在请求头里回传Session 就能自动搞定不用手动去维护 Cookie 字典。2.3 接口参数与“加密”问题到底要不要做 JS 逆向很多人一提到企查查就觉得所有接口都有加密参数必须得去抠 JavaScript。这种印象基本是被网上各种“逆向”博文带偏了。实际上搜索关键词联想这个接口我拿到的请求参数只有key、pageIndex、pageSize这种明文参数连个时间戳都没混进去。也就是说很多基础数据其实是通过简单的 GET 请求返回的并不存在所谓的加密门槛。那为什么还会有那么多“接口逆向”的需求因为当你想抓的是详情页、股东信息、对外投资这种更深层的数据时服务器会增加额外的校验参数。这些参数可能是由前端脚本生成的签名后端验签通过才返回数据。如果只是要“企查查企业基本信息”这种列表级数据不一定非要碰逆向。我的建议是先老老实实把浏览器开发者工具里的请求看一遍如果发现参数里有个token、sign这种看起来不明觉厉的东西再考虑要不要走逆向路线。多数情况下列表页的接口比想象中要友好。3. 核心代码实现requests 请求与数据解析3.1 环境准备与依赖安装开始写代码前先把环境搭好。我推荐用 Python 3.8 以上的版本因为后续可能要处理一些异步或者更高级的特性高版本兼容性更好。装库很简单命令行里跑两行pip install requests pip install lxml有的人喜欢用beautifulsoup4我在这里用 lxml 主要是解析性能好而且支持 XPath定位元素效率高。requests 用来处理 HTTP 请求lxml 用来解析 HTML 文本如果接口能直接返回 JSON 文件那连 lxml 都用不上直接用内置的json模块就行。为了让代码更通用我会同时演示“解析 JSON 接口”和“解析 HTML 页面”两种方式。3.2 构造请求头与会话那我把刚才从 cURL 里复制下来的请求头整理成 Python 字典。注意请求头里的Cookie并不是必须的——第一次请求时Session 里还没有 Cookie但服务器可能依然会返回数据。不过为了保证成功率我还是建议把浏览器里现有的 Cookie 先粘进去至少在第一次联调时能减少一个变量。下面这段代码我把它放在所有请求的最前面相当于一个公共模板import requests headers { 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, Referer: https://www.qcc.com/, Accept: application/json, text/plain, */*, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Connection: keep-alive, } s requests.Session() s.headers.update(headers)之所以用 Session除了自动管理 Cookie还有一个好处是它默认会维持连接池对于后面要做的多次请求性能会有明显提升。如果你在测试时被返回 403优先检查这里的User-Agent和Referer是不是和浏览器里一致。很多时候问题就出在这两个字段上。3.3 发送搜索请求并获取 JSON接过上一步的 Session我们来发送第一个真实请求。以“腾讯科技”为例搜索建议接口的 URL 长这样具体路径以自己的抓包结果为准不同时期可能会有调整search_url https://www.qcc.com/api/search/searchTips params { key: 腾讯科技, pageIndex: 1, pageSize: 10, } resp s.get(search_url, paramsparams, timeout10) print(resp.status_code) print(resp.text[:500]) data resp.json() for item in data.get(result, []): print(item.get(KeyNo), item.get(Name), item.get(OperateName))如果一切正常你会看到返回的是 JSON里面包含企业唯一编号KeyNo、企业名称Name、法定代表人OperateName这些字段。这个KeyNo很重要后续如果要查企业详细信息通常都要用到它作为查询参数。这里有个很值得注意的地方resp.text[:500]只是打印前 500 个字符来做快速验证。如果返回的是乱码说明编码识别有问题可以在代码里加上resp.encoding utf-8因为企查查接口返回的编码基本都是 UTF-8。如果返回的是 HTML 而不是 JSON那大概率是触发了风控被重定向到验证页面了这时候就需要回头检查请求头或者降低请求频率。3.4 数据清洗与保存到 CSV拿到 JSON 之后解析就是纯体力活。但需要注意字段的健壮性因为有些企业可能缺少法人信息或者名称为空。我在代码里做了一个简单的默认值处理避免KeyError。然后通过csv模块把数据写入文件这里要注意设置encodingutf-8-sig否则用 Excel 打开 CSV 文件时中文会乱码。import csv rows [] for item in data.get(result, []): row { 企业名称: item.get(Name, ), 法人代表: item.get(OperateName, ), 企业ID: item.get(KeyNo, ), 注册资本: item.get(RegistCapi, ), 成立日期: item.get(StartDate, ), 状态: item.get(Status, ), } rows.append(row) with open(company.csv, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnames[企业ID, 企业名称, 法人代表, 注册资本, 成立日期, 状态]) writer.writeheader() writer.writerows(rows)这样一套下来哪怕只看前 10 条数据也已经能明显感受到“从搜索到落盘”的完整链路了。实际项目里你还需要把搜索关键词做成参数循环把分页参数翻起来这样一天下来就能攒不少数据。不过别急后面我会专门讲频率控制不然你很可能抓了几百条就触发封禁。4. 反爬应对策略与合规提速4.1 常见反爬手段与识别方法心心念念的“无限制”只存在于理想状态。企查查虽然没有强制登录但不代表它不设防。我实际遇到过的反爬手段大致有三种请求频率限制、IP 封禁、验证码弹窗。你可以把它们想象成小区的三道门禁第一道看你是不是“人”请求头第二道看你今天进出了多少次频率第三道是临时抽查验证码。请求头这道门上面已经解决了。频率限制的识别很直观——请求连续超过某个阈值比如每秒 5 次后续请求就会开始返回429 Too Many Requests或者直接返回一段 HTML 让你去完成“滑动验证”。IP 封禁的表现为不管怎么改请求头返回都是403 Forbidden这时候就需要换一个出口 IP。处理频率限制的常规做法是在代码里加随机延时import time import random # 每次请求后休眠 1~3 秒 time.sleep(random.uniform(1, 3))很多新手觉得加延时“太慢”想直接用并发提升速度。我劝你克制一下。企查查的服务器对单 IP 的并发容忍度并不高我实测过 10 个并发线程同时请求跑了不到 30 秒IP 就被限流了。反倒是模拟人类操作的慢速爬取能稳定运行更久。4.2 代理IP池的使用边界既然单 IP 容易被封很多人自然想到代理 IP。我不反对用代理池但我要强调场景只有在你有明确的合规授权、且需要短时间内完成大数据量采集时才值得上。学习阶段完全没必要因为你连基础流程都没跑稳代理池只会引入更多变量比如代理本身不可用、响应超时、甚至代理商的非法 IP 来源风险。如果真的要用我建议优先考虑云厂商提供的按量付费代理服务而不是去爬那些免费代理列表——免费代理的几乎全不可用而且很可能本身就是蜜罐。合规的代理池接入也不复杂只需要在 requests 里设置proxies参数proxies { http: http://user:passip:port, https: http://user:passip:port, } resp s.get(search_url, paramsparams, proxiesproxies, timeout10)但请你记住代理只是绕开 IP 封锁并不能让你的爬取行为变得“合法”。真正的安全边界在于数据用途和访问频率。对企查查这种公开平台最好的姿态是“像一个普通用户一样浏览”而不是“刷爆服务器”。4.3 并发设计从单线程到多线程的正确姿势虽然不想让大家一上来就并发但热词里反复提到的“爬虫并发设计”确实是个值得讲清楚的话题。单线程的问题在于带宽利用率低特别是搜索关键词多、每轮请求都要等服务器响应时那几秒的网络延迟把整体速度拖慢不少。多线程的核心思路是网络等待期间让 CPU 去处理其他请求用时间换吞吐量。Python 的concurrent.futures用起来很顺手。我提供一个简化版的多线程模板但请留意我在代码里加的“信号量”和“延时”这不是摆设from concurrent.futures import ThreadPoolExecutor, as_completed import threading import time import random # 控制并发上限为 3避免瞬间把服务器打懵 semaphore threading.Semaphore(3) lock threading.Lock() def fetch_company(keyword): with semaphore: params {key: keyword, pageIndex: 1, pageSize: 10} try: resp s.get(search_url, paramsparams, timeout10) resp.raise_for_status() # 无论请求是否成功都随机休眠 1~2 秒 time.sleep(random.uniform(1, 2)) return resp.json() except Exception as e: print(f请求失败 {keyword}: {e}) return None keywords [腾讯, 阿里, 百度, 字节, 美团] with ThreadPoolExecutor(max_workers3) as executor: future_to_keyword {executor.submit(fetch_company, kw): kw for kw in keywords} for future in as_completed(future_to_keyword): result future.result() if result: # 这里只做示例实际处理更复杂 print(f获取到 {len(result.get(result, []))} 条数据)看到重点了吗并发上限默认是 1但 ThreadPoolExecutor 则可以灵活调整。把max_workers设为 3而不是 50是我反复试错后的经验值。企查查的反爬阈值大约在单个 IP 每秒 5~10 次请求我们带着延时再开 3 个线程实际 QPS 会稳定在 1 左右相对安全。等以后你换到更高级的代理池并做了租期管理再把并发数往上提也不迟。5. 常见问题与排查技巧实录5.1 请求返回 403 Forbidden怎么排查403 是所有爬虫开发者的老朋友。这个状态码意味着“服务器认识你但不想让你进”。排查步骤我固定是三步对比浏览器请求和代码请求的请求头是不是少了Referer或Accept。看 Cookie 是否需要更新有时浏览器里的 Cookie 带有登录态复制后代码里却又过期了。确认自己是不是被 IP 限流了最简单的方法是用浏览器访问一次同样的 URL能开就是 IP 被封打不开就说明还不是封禁。如果确认是 IP 封禁而且不是永久封禁那就歇几个小时再继续。如果只靠一个 IP 想长期稳定采集就得准备一个代理池但这超出了本文初级教程的范围。5.2 返回数据为空或者 JSON 里缺少要的字段这种情况通常不是代码逻辑问题而是接口的参数变了。企查查的搜索建议接口偶尔会调整参数名比如把pageSize改为limit。解决方法是重新打开浏览器开发者工具找最新的请求看看实际发送的参数然后同步更新代码。我建议不要把所有参数硬编码而是维护一个配置文件把参数名映射做成动态的这样下次接口变动时改配置比改代码要省事得多。另一种可能是搜索关键词太特殊没有匹配到任何企业此时返回的result可能是空数组。所以我写解析代码时要先判断result是否存在再遍历否则很容易抛TypeError。5.3 验证码弹出与处理策略验证码是反爬的“终极武器”。我在测试中遇到过几次真实场景连续高频访问某个详情页时页面弹出了滑块验证。这时候不要慌也不要试图去破解它——破解滑块属于对抗技术安全性非常敏感。我的建议是立即停止程序让 IP 休息至少 15 分钟以上再启动时把请求频率降为原来的三分之一。如果业务确实需要大量请求那就得接入验证码识别服务但这些服务的使用同样受限于网站条款而且价格不便宜。对于个人学习来说遇到验证码就是最好的停止信号说明你的爬虫行为“太像爬虫了”应该调整节奏而不是硬刚。5.4 输出 CSV 乱码与编码问题CSV 中文乱码是新手最容易碰到的问题。解决方式我已经在前面代码里提过使用utf-8-sig编码写入。注意不是utf-8而是增加 BOM 的utf-8-sigExcel 才能正确识别。如果你发现输出到终端的文本乱码那就是终端编码不对。可以在代码开头加一句import sys sys.stdout.reconfigure(encodingutf-8)Windows 下的 CMD 和 PowerShell 也建议先执行chcp 65001切换到 UTF-8 代码页否则看着乱码很难受。6. 稳定运行的实操心得与后续扩展这一节算是我自己踩坑踩出来的经验总结。第一请求频率永远是第一位我见过太多人为了赶进度把 IP 跑死最后不得不等待一天才能继续。第二数据源不止一个企查查、天眼查、爱企查都有公开数据如果某个平台严了可以考虑换一个目标不要死磕。第三代码模块化很重要把“构造请求”“解析数据”“存储数据”拆成独立函数方便后续替换数据源或者增加逻辑。最后再分享一个小技巧把抓包过程中保存的 curl 命令保留下来做成一个curl2python.py的脚本用正则把请求头转换为 requests 的 headers 字典。这样当你换了关键词、换了接口只要更新 curl 命令就能迅速生成一份可运行的新代码大大缩短开发时间。如果后续你想更进一步可以在这个基础上做一个可视化界面用 Flask 写个简单的 web 服务输入关键词就能在页面上展示结果甚至加一个定时任务每天自动抓取一批新增企业。到那一步你就已经跨出纯爬虫的范畴进入“数据采集 业务展示”的完整链路了。
返回列表