Healthline 是网络上访问量最大的健康与养生媒体之一,拥有涵盖营养、健身、疾病及心理健康的大量经医学审查的文章。每篇公开文章都附带一层真正有价值的结构化元数据:标题、作者署名、发布或更新日期、分类,以及一段简短摘要。这些元数据可用于内容研究、跨健康主题的趋势分析,以及梳理大型出版商报道内容的结构化目录,完全不需要触碰文章正文。

本指南将向您展示如何使用 Python 抓取 Healthline 的公开文章元数据,并将结果导出为 CSV。您将构建一个小型可运行的爬虫,通过 Crawling API 获取渲染后的搜索或列表页面,使用 BeautifulSoup 解析每条结果,并将整洁的数据行写入磁盘。整个演示仅限于公开元数据的范围。Healthline 的文章正文属于受版权保护的编辑内容,因此我们只采集结构和摘要用于研究,绝不完整抓取全文用于再发布。

您将构建什么

一个 Python 脚本,接收 Healthline 的公开列表或搜索 URL,通过 Crawling API 获取渲染后的 HTML,并为页面上的每篇文章提取结构化记录。我们以主题搜索为示例,逐篇抓取以下公开元数据字段:

  • 文章标题:该文章的标题,例如"抑酸药与偏头痛风险升高相关"。
  • URL:指向公开文章页面的规范链接。
  • 作者:署名信息,当 Healthline 在页面上公开时。
  • 发布或更新日期:文章发布或最近审查的日期。
  • 分类:文章所属的板块,如健康新闻或营养。
  • 摘要:Healthline 在标题下方显示的简短描述或导语。

为什么普通请求在 Healthline 上会失败

如果您使用基础 HTTP 客户端请求 Healthline 的列表或搜索 URL,会得到状态码 200 的响应,但正文中几乎没有任何文章数据。Healthline 在浏览器中通过 JavaScript 渲染其结果卡片,因此初始 HTML 只是一个外壳,需要页面脚本运行后才会填充内容。搜索结果页面尤其是在客户端从数据源组装的,这意味着您所需的标题、链接和摘要根本不在普通 requests 调用返回的原始标记中。

因此,一个可用的 Healthline 爬虫在单次请求中需要两件事:能够实际渲染页面的浏览器,以及一个让网站认为是真实访客的 IP 地址。您可以自行组装无头浏览器和轮换住宅代理池,但维护这套组合才是大部分工作所在。Crawling API 将两者合并为一次调用:您发送带有 JavaScript 令牌的 URL,它在可信 IP 后面渲染页面,并返回完整的 HTML 供您解析。

为什么需要 JS 令牌

Crawlbase 提供两种令牌类型。普通令牌获取静态 HTML;JavaScript (JS) 令牌则先在真实浏览器中渲染页面。Healthline 是客户端渲染的,因此此处需要使用 JS 令牌。使用普通令牌返回的结果与普通请求一样,只是一个空壳,无法从中解析出任何内容。

前提条件

开始编写代码前,您需要准备几样东西,每样都不需要很长时间。

基础 Python 知识。您应能编写和运行 Python 脚本,并使用 pip 安装包。如果您是 HTML 解析的新手,我们关于如何在 Python 中使用 BeautifulSoup 的入门指南涵盖了本教程所需的选择器基础知识。

Python 3.8 或更高版本。使用 python --version 确认版本。如果尚未安装,请从 python.org 安装,或通过 Anaconda 等发行版安装。

Crawlbase 账户和 JS 令牌。注册后,打开控制台,从账户文档页面复制您的 JavaScript (JS) 令牌。像对待密码一样保管令牌:它用于验证您的请求身份,请勿将其提交到版本控制系统。免费套餐包含 1,000 次请求,足以完整跟随本指南。

项目设置

创建项目文件夹和虚拟环境,确保依赖项相互隔离,然后安装爬虫所需的三个库。

bash
mkdir healthline_scraper
cd healthline_scraper

python -m venv healthline_env
source healthline_env/bin/activate

pip install crawlbase beautifulsoup4 pandas

在 Windows 上,使用 healthline_env\Scripts\activate 代替 source 行来激活环境。三个依赖项各司其职:crawlbase 是 Crawling API 的官方客户端,beautifulsoup4 解析返回的 HTML 以便通过 CSS 选择器提取各字段,pandas 将记录结构化并写入 CSV。

第一步:获取渲染后的列表页面

首先获取完整的页面。导入 CrawlingAPI 类,用您的 JS 令牌初始化,然后请求列表 URL。在解析之前检查状态码,可以让失败情况明显可见而不是悄悄忽略。注意两个等待选项:ajax_wait 告诉 API 等待异步内容加载完成,page_wait 则持续等待固定毫秒数,以便延迟渲染的卡片在页面被捕获前出现。

python
from crawlbase import CrawlingAPI

api = CrawlingAPI({"token": "YOUR_CRAWLBASE_JS_TOKEN"})

def crawl(page_url):
    options = {"ajax_wait": "true", "page_wait": 5000}
    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://www.healthline.com/search?q1=migraine"
    html = crawl(page_url)
    print(html[:500] if html else "No HTML returned")

使用 python scraper.py 运行脚本,您应该能看到真实的结果卡片标记,而不是普通请求返回的空壳。5 秒是合理的初始 page_wait 值;如果卡片为空则可以适当增加。在编写任何选择器之前,先确认渲染能正常工作。

Crawlbase Crawling API

Healthline 需要在一次调用中获得可信 IP 后面的完整渲染页面,这正是您在第一步中确认的。Crawling API 接受 JS 令牌,在真实浏览器中运行页面使客户端文章卡片出现,并在服务端轮换住宅 IP,因此您无需自行运行无头浏览器群和代理池。先在免费套餐上指向公开搜索页面试试看。

第二步:检查结果卡片结构

在编写选择器之前,在浏览器中打开 Healthline 搜索或列表页面,用开发者工具检查结果卡片。Healthline 使用经过哈希处理的、由构建系统生成的类名,因此确切字符串会随时间变化。在撰写本文时,每个搜索结果通过一个类似 css-17zb9f8<a> 链接出去,简短描述则在相邻的 <div class="css-1evntxy"> 中。您所需的字段大致如下映射:

  • 文章标题和 URL 位于结果链接上:链接文本是标题,href 是公开文章 URL。
  • 摘要 在描述块中,即 Healthline 在每条结果标题下方显示的简短导语。
  • 分类 可从 URL 路径中读取,例如 /health-news//nutrition/,Healthline 以此作为板块前缀。

作者和日期并不总出现在列表卡片上;它们位于文章页面本身,第四步将介绍。由于 Healthline 的类名经过哈希处理并在每次部署时更新,将其视为起始模板而非固定约定,当某个字段返回空值时请重新检查线上页面。

第三步:解析列表并导出为 CSV

拿到渲染后的 HTML,将其加载到 BeautifulSoup 中,选取每个结果链接,从中提取标题、URL、摘要和分类。用防御性读取包裹字段,使缺失的元素返回空字符串而不是导致运行崩溃,然后将记录交给 pandas 写入 CSV。

python
from bs4 import BeautifulSoup
import pandas as pd

def category_from_url(url):
    parts = url.split("/")
    return parts[3] if len(parts) > 3 else ""

def parse_listing(html):
    soup = BeautifulSoup(html, "html.parser")
    articles = []
    for link in soup.select('a.css-17zb9f8'):
        url = link.get("href", "")
        if not url:
            continue
        summary_el = link.find_next("div", class_="css-1evntxy")
        articles.append({
            "title": link.get_text(strip=True),
            "url": url,
            "category": category_from_url(url),
            "summary": summary_el.get_text(strip=True) if summary_el else "",
        })
    return articles

def save_to_csv(data, filename):
    df = pd.DataFrame(data)
    df.to_csv(filename, index=False)
    print(f"Saved {len(data)} rows to {filename}")

结果链接被读取一次,同时获取标题和 href;分类从 URL 路径派生,无需单独的选择器;摘要通过防御性读取获得,当描述块缺失时返回空字符串。save_to_csv 辅助函数将记录转换为 pandas 的 DataFrame 并使用 to_csv 写出,本指南后续内容都以此导出为基础。

选择器会漂移

Healthline 的标记无预告即会变动,上述经过哈希处理的类名在任意一次部署时都可能被重命名。当某个字段在所有卡片中都返回空值时,请在浏览器开发者工具中重新检查线上页面并更新选择器。定期维护选择器对任何生产级爬虫来说都是正常的,并不意味着出了什么问题。

第四步:从文章页面补充作者、日期和分类

列表给了您标题、URL 和摘要。要补充作者和发布或更新日期,需要获取每篇文章页面并读取其公开元数据。Healthline 在 <h1> 中显示标题,在带有 data-testid="byline" 属性的块中显示作者署名,在 <time> 元素中显示日期,其 datetime 属性包含机器可读的时间戳。我们只读取这些元数据,而不触碰文章正文。

python
def text_or_empty(soup, selector):
    el = soup.select_one(selector)
    return el.get_text(strip=True) if el else ""

def parse_article_meta(html, url):
    soup = BeautifulSoup(html, "html.parser")
    time_el = soup.find("time")
    return {
        "title": text_or_empty(soup, "h1"),
        "url": url,
        "author": text_or_empty(soup, '[data-testid="byline"]'),
        "date": time_el.get("datetime", "") if time_el else "",
        "category": category_from_url(url),
    }

每个字段都有回退处理,因此缺失的署名或日期会返回空字符串而不是抛出异常。<time> 元素的 datetime 属性优于可见日期文本,因为它已经是一致的 ISO 格式,一旦数据进入 CSV 后排序和筛选就非常简便。注意这个函数有意不采集的内容:文章正文的段落。我们只取标题、作者、日期、分类,以及来自列表的摘要。

第五步:整合在一起

现在将列表抓取、逐篇文章补充信息和 CSV 导出组合成一个可运行的脚本。获取列表,从中解析 URL,为每篇文章获取元数据,最后将所有内容写入一个 CSV 文件。请求之间的短暂停顿可以让运行过程保持礼貌。

python
import time
from crawlbase import CrawlingAPI
from bs4 import BeautifulSoup
import pandas as pd

api = CrawlingAPI({"token": "YOUR_CRAWLBASE_JS_TOKEN"})
OPTIONS = {"ajax_wait": "true", "page_wait": 5000}

def crawl(page_url):
    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

def main():
    listing_url = "https://www.healthline.com/search?q1=migraine"
    listing_html = crawl(listing_url)
    if not listing_html:
        return

    listing = parse_listing(listing_html)
    records = []
    for item in listing:
        article_html = crawl(item["url"])
        if article_html:
            meta = parse_article_meta(article_html, item["url"])
            meta["summary"] = item["summary"]
            records.append(meta)
        time.sleep(2)

    save_to_csv(records, "healthline_articles.csv")

if __name__ == "__main__":
    main()

此处假设来自前面步骤的 parse_listingparse_article_metacategory_from_urlsave_to_csv 都在同一个文件中。流程很直接:一次请求获取列表,然后逐篇请求文章以补充作者和日期,最后一次性写入 CSV。列表卡片的 summary 被合并到每篇文章的元数据中,确保每行数据完整。

输出示例

使用 python scraper.py 运行完整脚本,您将得到一个 CSV 文件,每行一篇文章,仅包含公开元数据,可直接在 pandas 或电子表格中分析。

csv
title,url,author,date,category,summary
"Antacids Associated with Higher Risk of Migraine",https://www.healthline.com/health-news/antacids-increase-migraine-risk,"Nancy Schimelpfening",2024-01-09,health-news,"New research suggests people who take antacids may be at greater risk for migraine attacks."
"Migraine: What to Ask Your Doctor",https://www.healthline.com/health/migraine/what-to-ask-doctor-migraine,"Healthline Editorial Team",2023-11-02,health,"A short list of questions to bring to your next appointment."

由于日期来自 <time> 元素的 datetime 属性,它以 ISO 格式落地,因此无需解析自由文本即可按时间排序或筛选日期范围。从每个 URL 路径派生的分类列让您可以按板块分组统计,从而了解出版商的报道重心所在。

跨主题和多页面扩展

单个列表只是演示;实际工作需要跨多个主题和结果页面运行。Healthline 的搜索接受查询参数,因此您可以循环遍历主题列表,并为每个主题复用同一套获取和解析逻辑。由于每个搜索结果共享相同的卡片结构,您已编写的解析器无需任何修改即可适用于所有主题。将每个主题的数据行追加到一个列表中,最后一次性写入 CSV。

python
def scrape_topics(topics):
    all_articles = []
    for topic in topics:
        url = f"https://www.healthline.com/search?q1={topic}"
        html = crawl(url)
        if html:
            all_articles.extend(parse_listing(html))
        time.sleep(2)
    return all_articles

topics = ["migraine", "nutrition", "sleep"]
save_to_csv(scrape_topics(topics), "healthline_topics.csv")

主题之间的 time.sleep(2) 是刻意为之的。在紧密循环中连续搜索是触发限速的最快方式,即使渲染和轮换都已为您处理好。分散请求,并在某个主题不再返回新文章时尽早停止。

保持不被封锁

即使渲染已被处理,Healthline 仍会监控具有爬虫特征的流量。以下几个习惯可以让长时间运行保持健康,它们适用于任何大型出版商。

  • 控制请求速率。分散请求,交替不同主题,而不是全速爬取同一搜索路径。
  • 依赖轮换。住宅 IP 池将请求分散到许多真实用户地址,不会让任何单一地址触发限速。Crawling API 为您处理这一切;如果您自建技术栈,这是最需要做对的部分。
  • 读取状态码。当运行开始返回挑战或错误时,说明当前速率或 IP 层级已不再足够。将其视为退让的信号,而不是可以忽略的噪声。

更广泛的策略请参阅如何在不被封锁的情况下抓取网站以及关于网络抓取中如何绕过 CAPTCHA 的深度指南。如果您希望通过轮换池路由自己的流量而不使用托管 API,Smart AI Proxy(也称 AI Proxy)提供与其相同的住宅 IP 轮换功能,作为即插即用的代理端点。

抓取 Healthline 是否合法?

抓取 Healthline 是否被允许,取决于 Healthline 的服务条款、您所在的司法管辖区以及您对数据的使用方式。Healthline 的条款限制自动访问,其内容属于受版权保护的编辑作品,因此无论您的工具多么谨慎,抓取都可能违反这些条款。本文中的代码并不会改变这一点,只是让技术层面的工作能够运行。请阅读 Healthline 的服务条款及其 robots.txt,并将两者视为您采集内容的边界。

几条值得坚守的底线。只采集公开元数据:文章标题、URL、作者署名、发布或更新日期、分类,以及任何人无需登录即可在公开页面看到的简短摘要。不要抓取并再发布完整的文章正文。Healthline 的文章是受版权保护的医疗和编辑内容,复制它们不仅是条款问题,更是版权问题。尊重 Healthline 明示的请求频率预期,将请求量控制在不给服务器造成压力的水平。

还有一点与健康类出版商特别相关:本指南是用于对公开元数据进行目录整理和研究,而非提供医疗指导。健康信息会随时间变化,文章的准确性取决于单行数据无法捕获的背景信息。不要依赖抓取到的健康内容做出医疗决策,在对任何内容采取行动或再发布之前,请咨询合格的医疗或法律专业人士。Healthline 不提供用于批量访问文章的公开 API,因此如果您的项目需要完整内容或大规模再发布,正确的做法是请求授权或签署许可协议,而不是设计更精巧的爬虫。

回顾

核心要点

  • Healthline 是客户端渲染的。普通请求返回的是空壳,因此必须在解析之前先渲染页面。
  • 渲染和可信 IP 相辅相成。带有 JS 令牌的 Crawling API 在一次调用中完成两者,使用 ajax_waitpage_wait 确保文章卡片加载完毕后再捕获。
  • BeautifulSoup 加 pandas 完成工作。将标题、URL、作者、日期、分类和摘要映射到页面钩子,然后直接将行导出为 CSV。
  • 通过循环主题实现扩展。用同一解析器遍历搜索查询列表,并控制循环节奏以避免被限速。
  • 坚守公开元数据范围。遵守 Healthline 的服务条款和 robots.txt,绝不再发布受版权保护的文章正文,并在依赖健康内容前咨询专业人士。

常见问题

我能仅用 requests 和 BeautifulSoup 抓取 Healthline 吗?

不够可靠。Healthline 在浏览器中通过 JavaScript 渲染其结果卡片,因此原始 requests 调用返回状态码 200 但列表为空。您需要某种东西先渲染页面,这正是 Crawling API 的 JS 令牌加上 ajax_waitpage_wait 选项在 BeautifulSoup 看到 HTML 之前处理的事情。

Healthline 需要普通令牌还是 JS 令牌?

JS 令牌。普通令牌获取静态 HTML,而 Healthline 上的静态 HTML 与普通请求返回的空壳相同。JS 令牌在返回 HTML 之前先在真实浏览器中渲染页面,因此当 BeautifulSoup 解析时文章卡片已经存在。

我应该从 Healthline 采集哪些数据?

只取公开元数据:文章标题、URL、作者、发布或更新日期、分类,以及公开页面上显示的简短摘要。不要抓取并再发布完整的文章正文。Healthline 的文章是受版权保护的编辑内容,因此安全且可辩护的范围是用于研究的结构和摘要,而非正文本身。

如何将抓取的数据导出为 CSV?

构建一个字典列表,每篇文章一个,然后使用 pd.DataFrame(data).to_csv("healthline_articles.csv", index=False) 传递给 pandas。本指南中的 save_to_csv 辅助函数正是对此的封装。由于每行只包含扁平的元数据字段,CSV 可以在任何电子表格中干净打开,或重新加载到 pandas 进行分析。

我的选择器返回空字符串,发生了什么变化?

几乎可以肯定是 Healthline 的标记发生了变化。解析器所依赖的哈希类名在每次部署时都可能更新,而重新设计可能会重命名署名或日期的钩子。在浏览器开发者工具中重新检查线上页面并更新选择器。定期维护选择器对任何生产级爬虫来说都是正常的。

抓取到的健康信息是否可以作为依据?

不能。将抓取到的行视为用于目录整理和研究的元数据,而不是医疗指导。健康信息会随时间变化,且依赖于单个字段无法捕获的背景信息,因此在采取行动或再发布任何内容之前,请咨询合格的医疗或法律专业人士。如需完整内容或大规模再发布,请向 Healthline 申请授权或许可,而不是抓取文章正文。

开始构建

大规模爬取任何站点,无需与基础设施对抗。

Crawlbase 负责处理代理、指纹和 CAPTCHA,让你的团队专注于交付数据流水线,而非维护爬取管道。1,000 次请求免费,无需信用卡。

自助开通 · 无需销售通话 · 提供企业级爬取量