首页 文章 精选 留言 我的

精选列表

搜索[Python],共10000篇文章
优秀的个人博客,低调大师

Python 爬虫技术栈:从静态页面提取到工程化抓取

爬虫的技术选型往往取决于目标页面的渲染方式与抓取规模。用一个代码案例贯穿始终并不现实:用 `requests` 抓取 JavaScript 动态渲染的页面会返回空壳 HTML,而用 Scrapy 去处理单页几个字段的临时任务又显得过重。本文按“静态页面提取 → 大规模工程化抓取 → 动态渲染处理”的递进路线展开,每个阶段附可运行的代码示例,最后补充反爬应对的实操策略。 --- ## 一、静态页面:requests + BeautifulSoup 的最小可行组合 对于服务端直接返回完整 HTML 的静态页面,`requests` 负责 HTTP 通信,`BeautifulSoup` 负责 DOM 解析,是最低学习成本的选择。`requests` 本身不解析页面,也不执行 JavaScript,它只拿回服务器返回的原始内容。这一限制决定了它只适合初始 HTML 中已包含目标数据的场景。 以练习站点为例,抓取首页所有书籍的标题与价格: ``` import requests from bs4 import BeautifulSoup def fetch_books(url: str) -> list[dict]: headers = { "User-Agent": ( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/122.0.0.0 Safari/537.36" ) } resp = requests.get(url, headers=headers, timeout=10) resp.raise_for_status() soup = BeautifulSoup(resp.text, "html.parser") books = [] for card in soup.select("article.product_pod"): title_tag = card.select_one("h3 a") price_tag = card.select_one("p.price_color") if not title_tag or not price_tag: continue books.append({ "title": title_tag.get("title", "").strip(), "price": price_tag.get_text(strip=True), "url": "https://books.ssjdxm.com/" + title_tag.get("href", ""), }) return books if __name__ == "__main__": for b in fetch_books("https://books.ssjdxm.com/"): print(b) ``` 这里有两个容易忽略的细节。其一,`raise_for_status()` 在状态码非 2xx 时抛出异常,避免后续解析空内容时产生难以定位的错误。其二,`select_one` 在未命中时返回 `None`,后续调用 `.get()` 或 `.get_text()` 会直接报 `AttributeError`,显式判空比事后调试更省时间。`BeautifulSoup` 对畸形 HTML 的容错性较好,适合结构不那么规整的页面;如果追求解析速度且 HTML 结构稳定,可以换用 `lxml` 作为解析器后端,XPath 表达力也更强。 --- ## 二、工程化抓取:Scrapy 的异步调度与结构化管道 当目标从“抓一页”变成“抓一万页”,手写循环加 `time.sleep` 的脚本在并发、去重、失败重试、数据导出上都会变得难以维护。`Scrapy` 把这些环节拆成独立组件:引擎负责调度,下载器负责请求,Spider 负责解析,Item Pipeline 负责清洗与存储。它的异步设计基于 Twisted,在同等网络条件下吞吐量显著高于同步脚本。 下面是一个完整的 Scrapy Spider,抓取全部书籍,并跟随“Next”按钮翻页: ``` import scrapy class BooksSpider(scrapy.Spider): name = "books_full" allowed_domains = ["wap.njjiamei.com"] start_urls = ["http://wap.njjiamei.com/"] def parse(self, response): for card in response.css("article.product_pod"): yield { "title": card.css("h3 a::attr(title)").get(default="").strip(), "price": card.css("p.price_color::text").get(default="").strip(), "detail_url": response.urljoin( card.css("h3 a::attr(href)").get() ), } next_page = response.css("li.next a::attr(href)").get() if next_page: yield response.follow(next_page, callback=self.parse) ``` 运行方式:`scrapy crawl books_full -o books.json`。`-o` 参数会根据文件后缀自动选择导出格式,JSON、JSON Lines、CSV 均可。`response.follow` 内部会调用 `urljoin` 处理相对路径,同时 Scrapy 自带的请求去重机制会阻止同一 URL 被重复抓取,翻页场景下不需要额外写去重逻辑。 Scrapy 的代价是学习曲线。项目结构、`settings.py` 中的并发与延迟配置、中间件机制,都需要一定时间熟悉。如果任务只是定期抓取少量页面,`requests + BeautifulSoup` 的脚本配合 cron 定时执行可能更直接。 --- ## 三、动态渲染:Playwright 处理 JavaScript 生成的页面 当目标数据由 JavaScript 在浏览器端渲染时,`requests` 拿到的 HTML 中只有空容器或占位脚本。`Playwright` 驱动真实浏览器引擎(Chromium、Firefox、WebKit),等待页面渲染完成后再提取内容。与 `Selenium` 相比,Playwright 的 API 设计更现代,自动等待机制减少了因元素未就绪而导致的随机失败。 以下示例等待书籍列表渲染完成后提取数据: ``` from playwright.sync_api import sync_playwright def scrape_dynamic(url: str) -> list[dict]: results = [] with sync_playwright() as p: browser = p.chromium.launch(headless=True) page = browser.new_page() page.goto(url, wait_until="networkidle") page.wait_for_selector("article.product_pod") cards = page.query_selector_all("article.product_pod") for card in cards: title_el = card.query_selector("h3 a") price_el = card.query_selector("p.price_color") results.append({ "title": title_el.get_attribute("title") if title_el else "", "price": price_el.inner_text() if price_el else "", }) browser.close() return results ``` `wait_until="networkidle"` 等待网络请求基本停止,`wait_for_selector` 确保目标元素出现在 DOM 中。Playwright 也支持拦截网络请求、复用浏览器上下文(context)来保持登录态,适合需要交互(点击、滚动、填表)才能加载数据的场景。代价是资源消耗:每个浏览器实例占用数百 MB 内存,吞吐量远低于纯 HTTP 方案,不适合大规模并发抓取。 --- ## 四、反爬应对的实操要点 即使代码逻辑正确,请求仍可能被 403 或虚假数据拦截。以下是可落地的策略,不涉及 JS 逆向等深度对抗。 **请求头伪装是第一道门槛。** Scrapy 默认的 User-Agent 是 `Scrapy/版本号`,许多站点会直接拒绝。在 `settings.py` 中替换为常见浏览器 UA 是最低成本的修复。`requests` 同理,至少携带 `User-Agent` 和 `Accept-Language`。 **频率控制不能靠固定间隔。** 固定 `time.sleep(0.1)` 产生的请求间隔过于规律,反而容易触发基于行为特征的风控。使用随机区间更接近人类浏览节奏: ``` import random, time def polite_delay(): time.sleep(random.uniform(0.8, 2.5)) ``` 在 Scrapy 中对应 `DOWNLOAD_DELAY` 配合 `RANDOMIZE_DOWNLOAD_DELAY = True`(默认开启)。如果并发开到 32 仍然秒被封,问题通常不在代码,而在于请求密度本身已经超出了目标站点的容忍阈值。 **代理不是万能药,但不换 IP 一定走不远。** 单一 IP 的请求量累积到一定程度后,即使频率很低也可能被信誉系统降权。代理轮换需要配合有效性检测,免费代理池的可用率极低,不值得投入时间调试。对于学习用途,控制总量、降低频率比折腾代理更实际。 **遵守 robots.txt 是底线。** Scrapy 的 `ROBOTSTXT_OBEY = True` 是默认配置,不要为了抓取本被禁止的路径而关闭它。目标站点的服务条款和版权声明同样需要关注,爬虫的技术能力不构成绕过这些约束的理由。 --- ## 选型速查 |场景|推荐方案|关键理由| | --- | --- | --- | |静态页面、少量字段、快速验证|requests + BeautifulSoup|代码直观,依赖少,调试成本低| |多页/多层级抓取、需要持久化与去重|Scrapy|异步调度、内置去重与导出,项目结构可维护| |数据由 JS 渲染、需要交互|Playwright(或 Selenium)|真实浏览器环境,能拿到动态生成的 DOM| |大规模抓取且对性能敏感|Scrapy + lxml 解析器|异步吞吐 + C 库解析速度| 爬虫的工程复杂度应当与任务规模匹配。一个只跑一次、抓取几百条数据的脚本,不值得引入 Scrapy 的完整项目结构;而需要持续运行、增量更新、多站点管理的采集任务,从 Scrapy 开始会比从脚本逐步演化更省力。

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

用户登录
用户注册