TechCrunch 每天发布数十篇关于初创公司、融资轮次、产品发布以及推动科技行业发展的人物的报道。每篇文章都包含一组整洁的公开元数据,这正是趋势追踪器、市场研究仪表盘或新闻室监控工具所需要的:标题、作者、发布时间、所属类别和标签、文章 URL 以及简短摘要。问题在于 TechCrunch 运行在经过强化的 WordPress 架构上,能够快速识别自动化流量,因此一个朴素的爬虫在采集到任何有用内容之前就会遭到挑战或屏蔽。
本指南介绍如何可靠地用 Python 抓取 TechCrunch。你将构建一个小型可运行的爬虫,通过 Crawling API 获取文章列表页面,用 BeautifulSoup 解析每张文章卡片,并输出干净的结构化数据。整个流程仅限于公开文章元数据,从不涉及完整文章正文;文末的合法性部分不是样板文字,在将此应用于任何实际规模之前请务必阅读。
你将构建的内容
一个 Python 脚本,接受公开的 TechCrunch 文章列表 URL,通过 Crawling API 获取 HTML,并为页面上每篇文章提取一条结构化记录。示例使用 TechCrunch 首页动态作为贯穿全文的案例,并从每张卡片中提取以下字段:
- 标题:文章列表中显示的文章题目。
- 文章链接:指向具体报道的链接。
- 作者:卡片上署名的作者。
-
发布日期:来自
datetime属性的机器可读时间戳。 - 类别和标签:文章所属的版块或话题。
- 摘要:标题下方显示的简短概述。
为什么普通请求在 TechCrunch 上会失败
你可以将 Python 的 requests 指向 TechCrunch 的 URL,有时也能拿到 HTML,但真正的抓取运行很少能持续顺利。TechCrunch 位于一个边缘层后方,该层会监测爬虫流量,有两件事对你不利。第一,来自数据中心 IP 以及不像真实浏览器的请求模式,在最初几次请求后就会遭到限速或被提供挑战页面,来自单一地址的重复访问会很快触发这一阈值。第二,某些文章列表和动态视图使用 JavaScript 填充内容,因此你获取到的原始 HTML 可能缺少你所需的卡片。
因此,一个可靠的 TechCrunch 爬虫需要在一次请求中同时满足两点:平台认为是真实访客的 IP,以及在视图是客户端渲染时能真正运行页面脚本的浏览器。你可以自己搭建一个带有轮换 IP 池加无头浏览器的方案,但将它们整合并保持健康运行才是工作量的主体。Crawling API 将两者融合为一次调用:你发送 URL,它在受信任的轮换 IP 后获取页面,可选择渲染 JavaScript,并返回供你解析的完整 HTML。
Crawlbase 提供两种 token 类型。普通 token 获取静态 HTML;JavaScript(JS)token 先在真实浏览器中渲染页面。TechCrunch 文章列表基本上是服务端渲染的 WordPress 标记,因此普通 token 在这里通常就够了。如果某个特定动态内容回来时卡片为空,请切换到 JS token 来渲染它。你可以从 1,000 次免费请求开始,无需信用卡。
前提条件
在编写代码之前,你需要准备好以下几项。每项配置都不需要太长时间。
Python 基础。你应该能够编写并运行 Python 脚本,并使用 pip 安装包。如果 BeautifulSoup 对你来说是新的,我们的Python 中使用 BeautifulSoup 指南涵盖了本教程所假设的解析基础。
Python 3.8 或更高版本。使用 python --version 确认版本。如果没有安装,请从 python.org 安装,或通过 Anaconda 等发行版安装。
Crawlbase 账户和 token。注册后,打开控制台,从账户文档页面复制你的普通 token。请像对待密码一样保管 token:它用于验证你的请求,不要放入版本控制。
配置项目
创建虚拟环境以隔离项目依赖,然后安装爬虫所需的库。
python --version python -m venv techcrunch_env source techcrunch_env/bin/activate pip install crawlbase beautifulsoup4 pandas
在 Windows 上,用 techcrunch_env\Scripts\activate 代替 source 那行来激活环境。三个依赖库各司其职:crawlbase 是 Crawling API 的官方客户端,beautifulsoup4 解析返回的 HTML 以便通过 CSS 选择器提取各字段,pandas 使得最后将记录写入 CSV 变得方便。
第一步:获取文章列表页面
首先获取页面。导入 CrawlingAPI 类,用你的 token 初始化,并请求文章列表 URL。在解析之前检查状态,可以让失败情况更加明显而非悄然发生。
from crawlbase import CrawlingAPI api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"}) def crawl(page_url): options = {"country": "US"} response = api.get(page_url, options) if response["status_code"] == 200: return response["body"].decode("utf-8") print(f"Request failed: {response['status_code']}") return None if __name__ == "__main__": page_url = "https://techcrunch.com" html = crawl(page_url) print(html[:500] if html else "No HTML returned")
country 选项将请求固定到美国出口 IP,这很重要,因为 TechCrunch 可能按地区提供不同内容。用 python scraper.py 运行脚本,你应该在前 500 个字符中看到真实的文章标记,而不是拦截页面或空外壳。这确认了获取能在受信任 IP 后正常工作,之后再编写选择器。如果卡片内容为空,请按上方标注说明改用 JS token 重新运行。
TechCrunch 会快速挑战数据中心 IP,而你刚才检查的状态 200 只有在请求来自平台信任的地址时才可靠。Crawling API 在服务端轮换住宅 IP,可选择渲染 JavaScript,并返回完整的 HTML,让你无需自己运行无头浏览器集群和代理池。先将它指向免费版中的公开文章列表页面。
第二步:用 BeautifulSoup 解析文章卡片
拿到 HTML 后,将其加载到 BeautifulSoup 中,通过选择器提取每篇文章。TechCrunch 将文章列表以重复块的形式布局,因此你只需一次性选取所有文章容器,然后从每个容器中读取相同的字段。在 WordPress 标记中,每篇文章位于带有 wp-block-tc23-post-picker 类的容器内,这是你循环的锚点。在浏览器开发者工具中检查实时页面以确认当前类名,因为这些标记会随时间变化。
from bs4 import BeautifulSoup def text_of(node, selector): found = node.select_one(selector) return found.get_text(strip=True) if found else "" def parse_listings(html): soup = BeautifulSoup(html, "html.parser") cards = soup.select("div.wp-block-tc23-post-picker") articles = [] for card in cards: title_el = card.select_one("h2.wp-block-post-title") link_el = title_el.select_one("a") if title_el else None time_el = card.select_one("time") articles.append({ "headline": title_el.get_text(strip=True) if title_el else "", "url": link_el["href"] if link_el else "", "author": text_of(card, "div.wp-block-tc23-author-card-name"), "publish_date": time_el["datetime"] if time_el else "", "category": text_of(card, "div.wp-block-tc23-post-picker__category a"), "excerpt": text_of(card, "p.wp-block-post-excerpt__excerpt"), }) return articles
两种模式使代码具有良好的容错性。text_of 辅助函数在选择器未命中时返回空字符串而非抛出异常,确保一张格式异常的卡片不会导致整次运行崩溃。而从 <time> 标签的 datetime 属性中读取发布日期,可以得到干净的 ISO 时间戳,而非卡片上显示的人类友好文本,这在下游排序和过滤时方便得多。类别选择器指向每个标题上方的小型话题链接;没有类别的卡片会产生空字符串。
第三步:组装完整脚本
现在将获取和解析整合到一个可运行的文件中,指向首页动态,并将记录写入磁盘。main 函数将各部分串联起来,并用 pandas 保存 CSV,使输出可以直接放入电子表格或笔记本中。
import json import pandas as pd from bs4 import BeautifulSoup from crawlbase import CrawlingAPI api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"}) def crawl(page_url): response = api.get(page_url, {"country": "US"}) if response["status_code"] == 200: return response["body"].decode("utf-8") print(f"Request failed: {response['status_code']}") return None def text_of(node, selector): found = node.select_one(selector) return found.get_text(strip=True) if found else "" def parse_listings(html): soup = BeautifulSoup(html, "html.parser") cards = soup.select("div.wp-block-tc23-post-picker") articles = [] for card in cards: title_el = card.select_one("h2.wp-block-post-title") link_el = title_el.select_one("a") if title_el else None time_el = card.select_one("time") articles.append({ "headline": title_el.get_text(strip=True) if title_el else "", "url": link_el["href"] if link_el else "", "author": text_of(card, "div.wp-block-tc23-author-card-name"), "publish_date": time_el["datetime"] if time_el else "", "category": text_of(card, "div.wp-block-tc23-post-picker__category a"), "excerpt": text_of(card, "p.wp-block-post-excerpt__excerpt"), }) return articles def main(): page_url = "https://techcrunch.com" html = crawl(page_url) if not html: return articles = parse_listings(html) print(json.dumps(articles[:3], indent=2)) pd.DataFrame(articles).to_csv("techcrunch_listing.csv", index=False) print(f"Saved {len(articles)} articles") if __name__ == "__main__": main()
这就是完整的爬虫。它获取首页动态,将每张卡片解析为包含六个公开字段的记录,以 JSON 格式打印前三条,并将完整集合写入 techcrunch_listing.csv。将 page_url 替换为任何公开文章列表(例如类别或标签动态),同样的解析器即可处理。
输出结果是什么样的
用 python scraper.py 运行完整脚本,你将得到每篇文章的干净结构化记录,可随时写入 JSON、CSV 或数据库。
[ { "headline": "Open source tools to boost your productivity", "url": "https://techcrunch.com/2024/08/11/a-not-quite-definitive-guide-to-open-source-alternative-software/", "author": "Paul Sawers", "publish_date": "2024-08-11T09:00:00-07:00", "category": "Apps", "excerpt": "TechCrunch has pulled together some open-source alternatives to popular productivity apps." }, { "headline": "Oyo valuation crashes over 75% in new funding", "url": "https://techcrunch.com/2024/08/11/oyo-valuation-crashes-over-75-in-new-funding/", "author": "Manish Singh", "publish_date": "2024-08-11T06:07:12-07:00", "category": "Fintech", "excerpt": "The valuation of Oyo, once India's second-most valuable startup at $10 billion, has dipped to $2.4 billion." } ]
注意摘要是简短概述,而非完整文章正文。这是有意为之的。文章列表卡片展示的是预告,而围绕它的元数据字段正是你进行趋势追踪所需的公开信号,而不会复制编辑文本本身。
循环多页并控制请求节奏
一个文章列表只是演示;实际工作需要跨多页运行。TechCrunch 的动态使用简单的 URL 模式分页:首页是 https://techcrunch.com,后续页面是 https://techcrunch.com/page/2/、https://techcrunch.com/page/3/ 等。结构保持不变:构建每个页面 URL,通过 Crawling API 获取,用相同函数解析,并收集各行数据。控制请求之间的间隔有助于长时间运行保持健康。
import time def scrape_pages(num_pages=5): results = [] for page in range(1, num_pages + 1): url = "https://techcrunch.com" if page == 1 else f"https://techcrunch.com/page/{page}/" print(f"Scraping page {page}") html = crawl(url) if html: results.extend(parse_listings(html)) time.sleep(3) return results
time.sleep 调用将请求分散开来,避免在紧循环中频繁请求 TechCrunch。由于每一页共享相同的卡片结构,你已经编写好的解析器无需修改即可适用于所有页面,并将合并后的列表传入完整脚本中相同的 pandas to_csv 调用。
保持不被屏蔽
即使有受信任的 IP 处理获取,TechCrunch 仍会监测爬虫流量。一些好习惯能让运行保持健康,这些习惯适用于任何有挑战性的目标。
- 控制请求节奏。在紧循环中频繁请求文章列表是最快被限速的方式。将请求分散开来,变换目标,而不是全速抓取单一动态。
- 依赖轮换机制。住宅 IP 池将请求分散到众多真实用户地址,使任何单一地址都不会触发限速。Crawling API 为你处理这一切;如果你自己搭建方案,这是最重要的部分。
- 读取状态码。开始返回挑战或错误的运行在告诉你当前速率或 IP 级别已不够。将其视为退让的信号,而非可忽略的噪声。
关于更广泛的策略,请参阅如何在不被屏蔽的情况下抓取网站和关于在网络抓取时绕过 CAPTCHA 的深度指南。如果某个特定动态是客户端渲染的,我们关于用 Python 抓取 JavaScript 页面的指南解释了渲染为何重要。如果你想通过轮换池路由自己的流量而不使用托管 API,Smart AI Proxy(也称为 AI Proxy)提供了与 Crawling API 相同的住宅 IP 轮换,作为即插即用的代理端点。
抓取 TechCrunch 是否合法?
抓取 TechCrunch 是否被允许,取决于 TechCrunch 的服务条款、你所在的司法管辖区以及你对数据的使用方式。TechCrunch 的条款限制了自动访问,且其内容是受版权保护的编辑作品,因此与公开商品列表网站相比,法律范围更为狭窄。本指南中的任何代码都不会改变这一点;它只是让技术部分得以实现。请阅读 TechCrunch 服务条款及其 robots.txt,并将两者视为采集边界。
保持可辩护性的界线在于元数据与文章本身之间的区别。采集公开元数据(标题、作者、发布日期、类别和标签、文章 URL 以及简短摘要)用于研究或趋势分析,远比复制完整文章正文轻微。不要重新发布或转载 TechCrunch 制作的编辑文字;那是受版权保护的媒体,转发它直接违反服务条款和版权法。如果你需要大规模获取底层报道,正确的途径是内容许可或官方协议,而非更复杂的爬虫。
还值得了解的是,TechCrunch 运行在 WordPress 上,这意味着有更轻便的官方途径可以获取大量数据。TechCrunch 发布了 RSS 动态,并在 /wp-json/wp/v2/posts 暴露了 WordPress REST API,返回最近文章的结构化 JSON,包括标题、链接、日期和摘要,完全无需抓取渲染后的页面。在这些端点能满足需求时,优先使用它们,并遵守其声明的速率限制。本指南仅限于公开文章列表页面和元数据,不涵盖登录后的任何内容、个人数据或全文转载。
核心要点
- TechCrunch 会屏蔽爬虫流量。普通请求会很快遭到限速或挑战,因此需要在受信任的轮换 IP 后获取。
- Crawling API 处理困难的部分。一次调用即可在住宅 IP 后获取页面,在需要时渲染 JavaScript,并返回可解析的完整 HTML。
-
BeautifulSoup 完成提取。选取每张
wp-block-tc23-post-picker卡片,然后从中读取标题、URL、作者、发布日期、类别和摘要,并预期选择器会发生变化。 -
从属性读取日期。
<time>标签的datetime属性提供干净的 ISO 时间戳,比显示文本更易排序和过滤。 - 坚守公开元数据。遵守 ToS 和 robots.txt,优先使用 TechCrunch 的 RSS 动态和 WordPress REST API,切勿转发完整文章正文。
常见问题
为什么普通请求在 TechCrunch 上会被屏蔽?
TechCrunch 位于一个标记自动化流量的边缘层后方。来自数据中心 IP 以及不像真实浏览器的请求模式,会在几次请求后遭到限速或被提供挑战页面,因此原始的 requests 循环很快就会失效。通过 Crawling API 路由请求,可使请求通过平台视为真实访客的住宅 IP,这正是保持运行持续的关键。
抓取 TechCrunch 需要普通 token 还是 JS token?
通常使用普通 token 就够了。TechCrunch 文章列表基本上是服务端渲染的 WordPress 标记,因此普通 token 返回的静态 HTML 中已包含文章卡片。如果某个特定动态返回的卡片为空,请切换到 JS token,它会在返回 HTML 前在真实浏览器中渲染页面。
我可以从 TechCrunch 文章列表中提取哪些字段?
每张卡片上的公开元数据:标题、文章 URL、作者署名、来自 <time> 标签 datetime 属性的发布日期、文章所属类别或标签,以及标题下方显示的简短摘要。本指南仅限于这些元数据,不提取受版权保护的完整文章正文。
是否有官方 API 可以替代抓取?
有。TechCrunch 运行在 WordPress 上,因此发布了 RSS 动态,并在 /wp-json/wp/v2/posts 暴露了 WordPress REST API,返回包含标题、链接、日期和摘要的结构化 JSON。在这些端点能满足需求时优先使用它们,因为它们是更轻便的官方途径,无需渲染。
我的选择器对每张卡片都返回空值。发生了什么变化?
几乎可以肯定是 TechCrunch 的标记发生了变化。WordPress block 类名(如 wp-block-tc23-post-picker)会在没有通知的情况下更改,因此上个月还有效的选择器可能已失效。在浏览器开发者工具中重新检查实时文章,并更新选择器。定期维护选择器对任何生产级爬虫来说都是正常的。
如何在抓取 TechCrunch 时避免被屏蔽?
保持较低的每 IP 请求速率,变换目标而非循环抓取单一动态,并通过轮换住宅 IP 路由,使任何单一地址都不会触发限速。Crawling API 为你管理轮换和受信任的 IP 池;如果你自己搭建方案,这是需要投入的部分。观察状态码,当开始出现挑战时适当退让。
大规模爬取任何站点,无需与基础设施对抗。
Crawlbase 负责处理代理、指纹和 CAPTCHA,让你的团队专注于交付数据流水线,而非维护爬取管道。1,000 次请求免费,无需信用卡。
