前言把 BeautifulSoup 和 Scrapy 放在同一个标题里很容易让人以为这是两套「抓取方案」在互相竞争。实际不是它们根本不在一个层级上。BeautifulSoup 是一个HTML 解析库——你给它一段 HTML 文本它帮你把标签树解析出来然后查找、取文本、取属性它不发请求不管调度。Scrapy 是一个爬虫框架——它负责发请求、排队、去重、重试、把数据送进管道解析只是它众多环节中的一个而且它自带的 css/xpath 选择器通常已经够用。所以真实的关系是Scrapy 是骨架BeautifulSoup 是可选的一把解析工具。那什么时候需要在 Scrapy 里再用 BeautifulSoup当你面对的是结构非常不规整的老网页css/xpath 写起来别扭而 BeautifulSoup 的「找标签、取文本、容错解析」反而更顺手时或者你已有的解析函数是基于 BeautifulSoup 写的想在 Scrapy 里复用时。除此之外不建议把两套解析方式在同一个项目里混着用。本文的代码全部基于 Python 3。顺带一提网上讲 BeautifulSoup 的老文章里常有 Python 2 写法比如print soup.title、soup.find(a).renderContents()之类Python 2.7 已于 2020 年 1 月 1 日停止维护请改写成 Python 3 的print(...)同理.iteritems()要改成.items()返回视图而非列表xrange要改成range本身就是惰性的。抓取任何站点前请先确认其 robots.txt 与使用条款允许你抓取并保持礼貌的请求频率。一、分工对照维度BeautifulSoupScrapy类型解析库爬虫框架发请求不发常配 requests内置下载器调度/去重无内置解析方式find/find_all/selectcss/xpath容错解析强坏 HTML 也能救依赖底层解析器并发无有适用单页面、小脚本、解析脏 HTML系统性抓取一句话BeautifulSoup 负责「怎么读懂这段 HTML」Scrapy 负责「怎么把网页抓回来并管理好」。二、BeautifulSoup 基础用法BeautifulSoup 需要一个解析器作为后端。内置的html.parser不需要额外安装适合入门lxml和html5lib解析更快或更接近浏览器行为需要另行安装。# 适用于 Python 3beautifulsoup4from bs4 import BeautifulSouphtml htmlbodydiv classquotespan classtexthello/spansmall classauthoralice/smalla href/author/aliceabout/a/divdiv classquotespan classtextworld/spansmall classauthorbob/small/div/body/htmlsoup BeautifulSoup(html, html.parser)# find 只取第一个匹配找不到返回 Nonefirst soup.find(div, class_quote) # 注意是 class_不是 class# find_all 取全部匹配找不到返回空列表for div in soup.find_all(div, class_quote):text div.find(span, class_text).get_text(stripTrue)author div.find(small, class_author).get_text(stripTrue)link div.find(a)# 属性可能不存在用 .get() 更安全href link.get(href) if link else Noneprint(author, |, text, |, href)几个必须记牢的点find和find_all里的class要写成class_class是 Python 关键字取标签内文本用get_text(stripTrue)stripTrue会去掉首尾空白取属性用tag.get(href)若属性不存在它会返回None而用tag[href]会直接抛KeyError。CSS 风格的选择用soup.select(div.quote)返回列表soup.select_one(...)返回第一个或None。三、Scrapy 的解析方式Scrapy 的响应对象自带选择器response.css()和response.xpath()是主力。它们的返回值是类列表对象.get()取第一个.getall()取全部。# 适用于 Python 3.8Scrapy 2.ximport scrapyclass QuotesSpider(scrapy.Spider):name quotesstart_urls [https://quotes.toscrape.com/]custom_settings {ROBOTSTXT_OBEY: True,DOWNLOAD_DELAY: 1.0,CONCURRENT_REQUESTS_PER_DOMAIN: 1,}def parse(self, response):for quote in response.css(div.quote):yield {text: quote.css(span.text::text).get(),author: quote.css(small.author::text).get(),tags: quote.css(div.tags a.tag::text).getall(),}Scrapy 的选择器语法里:text取文本::attr(href)取属性# 适用于 Python 3.8Scrapy 2.xhrefs response.css(a::attr(href)).getall()titles response.xpath(//h1/text()).getall()大多数静态页面用这套就解决了不需要再引入 BeautifulSoup。四、两库结合只在需要时当解析逻辑确实更适合 BeautifulSoup 时可以在 Spider 的回调里用response.text构造一个BeautifulSoup对象# 适用于 Python 3.8Scrapy 2.x解析用 beautifulsoup4import scrapyfrom bs4 import BeautifulSoupclass LegacySpider(scrapy.Spider):name legacystart_urls [https://quotes.toscrape.com/]def parse(self, response):# response.text 是解码后的字符串交给 BeautifulSoup 解析soup BeautifulSoup(response.text, html.parser)for div in soup.find_all(div, class_quote):yield {text: div.find(span, class_text).get_text(stripTrue),author: div.find(small, class_author).get_text(stripTrue),}这样做是可用的但要有心理准备BeautifulSoup 解析整页会比 Scrapy 原生的选择器慢因为它要先把整棵树建出来而且一旦混用团队里就得同时维护两套查找语法。建议是「能不用就不用」——只有在页面结构实在太脏、css/xpath 表达力不够时才引入。如果你是先用requests抓、再用 BeautifulSoup 解析那也没问题只是要自己处理编码、Cookie、重试和限速。抓取的请求头里请标明真实的爬虫身份。常见坑点1.find和find_all用反❌for div in soup.find(div):find返回单个标签或None不可迭代。 ✅ 循环用find_all只要第一个才用find并记得判None。2. 拿属性时下标访问❌href tag[href]属性不存在就抛KeyError。 ✅href tag.get(href)不存在返回None。3. 关键词class直接写❌soup.find(div, classquote)class是 Python 关键字语法错误。 ✅soup.find(div, class_quote)。4. 忘记stripTrue文本带一堆空白❌get_text()返回\n hello\n 比较时永远不相等。 ✅get_text(stripTrue)。5..string与.get_text()混淆❌ 对一个含多个子节点的标签用.string拿到None。 ✅ 要取全部后代文本用.get_text().string只在标签只有一个文本子节点时才有值。6. 在 Scrapy 里用response.body构造 BeautifulSoup❌BeautifulSoup(response.body, html.parser)body是字节串中文可能乱码。 ✅ 用解码后的response.text。7. 装了lxml却指定了不存在的解析器名❌BeautifulSoup(html, lxml-xml)却没装对应后端抛出FeatureNotFound。 ✅ 入门用内置的html.parser要更快再用lxml并确保已安装。8. 把解析库当成爬虫❌ 用 BeautifulSoup 写爬虫却不处理限速、重试、robots。 ✅ 需要调度与合规控制时用 ScrapyBeautifulSoup 只管解析。总结你想做的事用哪个关键写法解析一段 HTMLBeautifulSoupBeautifulSoup(html, html.parser)找第一个 / 全部BeautifulSoupfind/find_all取文本 / 属性BeautifulSoupget_text(stripTrue)/get(href)发请求 解析Scrapyresponse.css/response.xpath取单个 / 多个值Scrapy.get()/.getall()翻页Scrapyresponse.follow()复用已有 bs4 解析两者结合BeautifulSoup(response.text, ...)BeautifulSoup 和 Scrapy 不是竞争关系而是「解析」与「框架」的关系。绝大多数场景下Scrapy 自带的选择器就够了只有当页面结构实在难缠时才把 BeautifulSoup 请进来当解析助手。先把该抓什么、能不能抓想清楚再决定用什么工具去读它。