首页
/ Scrapling Selector 类完整 API 参考:HTML 解析、元素选择与数据抽取的底层实现

Scrapling Selector 类完整 API 参考:HTML 解析、元素选择与数据抽取的底层实现

2026-09-04 22:15:51作者:吴年前Myrtle

本文围绕 docs/api-reference/selector.md 所引用的 SelectorSelectors 两个核心类展开,逐一说明其构造参数、属性、选择方法、自适应(adaptive)机制与文本抽取能力,并结合 scrapling/parser.py 的源码实现印证各参数的默认值、取值范围与内部行为。读完本篇,你可以完整掌握 Scrapling 解析引擎的 API 形态,并能直接复制示例代码完成从静态 HTML 到结构化数据的抽取。

类的定位与导入方式

Selector 是 Scrapling 的核心解析引擎,提供 HTML 解析与元素选择能力。根据 scrapling/parser.py 的定义,Selector 继承自 SelectorsGeneration(一个提供选择器生成能力的 mixin,见 scrapling/core/mixins.py),并不直接继承 lxml.html.HtmlElement。源码注释解释了这一设计原因:lxml.html.HtmlElement 无法被 pickle 序列化(会抛出 AssertionError: invalid Element proxy at...),因此改为"包装"(wrapper)而非继承,以保留更多引用与跨进程能力。

导入方式有两种,均可用:

from scrapling import Selector
from scrapling.parser import Selector

其中 from scrapling import Selector 依赖包级惰性导入机制——scrapling/init.py 中的 _LAZY_IMPORTS 映射表在首次访问时才真正导入 scrapling.parser 模块,避免不必要的导入开销。

Selector 构造参数详解

Selector.__init__ 定义在 scrapling/parser.pycontentroot 二者必须至少提供一个,否则抛出 ValueError("Selector class needs HTML content, or root arguments to work")

参数 类型 默认值 说明
content str | bytes None HTML 内容,支持字符串或字节
url str "" 与 HTML 数据关联的 URL,便于后续 urljoin 还原绝对地址
encoding str "utf-8" 解析 HTML 时使用的编码
huge_tree bool True 启用 libxml2 特性,解析大文档时防止内存耗尽,默认开启
root HtmlElement None 内部使用,直接传入 etree 对象,优先级最高
keep_comments bool False 解析时是否保留 HTML 注释,默认丢弃
keep_cdata bool False 解析时是否保留 CDATA,默认为更干净的 HTML 而丢弃
adaptive bool False 全局开关自适应功能,优先级高于所有 adaptive 相关方法参数
storage class SQLiteStorageSystem 自适应功能使用的存储类,默认 SQLite
storage_args Dict None 传给存储类的参数字典,为空则用默认值
_storage instance None 内部参数,直接传入已构造的存储实例

从源码 scrapling/parser.py 可以看到几个实现细节:

  • str 类型的内容会先 strip() 并剔除空字节 \x00,若结果为空则回退为 "<html/>"bytes 类型剔除 b"\x00"
  • 内部使用 lxml.etree.HTMLParser 构造解析器,固定传入 recover=True(容错解析)、remove_blank_text=Truecompact=Truedefault_doctype=True,并根据 keep_comments/keep_cdata 动态设置 remove_commentsstrip_cdata
  • 若启用 adaptive,存储类必须用 lru_cache 装饰器包裹(否则抛出 ValueError),且必须继承 StorageSystemMixin,否则同样报错。默认存储文件路径为包内的 elements_storage.db(见 scrapling/parser.py__DEFAULT_DB_FILE__)。

基础用法示例(沿用 docs/parsing/main_classes.md 的 HTML):

from scrapling import Selector

page = Selector(
    '<html><body><div class="product-list">'
    '<article class="product" data-id="1"><h3>Product 1</h3></article>'
    '</div></body></html>',
    url='https://example.com'
)

article = page.find('article')

元素属性与文本属性

以下属性均为惰性加载tagtextattribhtml_content 等在被首次访问时才计算并缓存到实例上。源码注释(scrapling/parser.py)明确说明这是为了把初始化成本降低——批量创建实例时不预执行这些计算,性能测试中提速显著。

  • tagL259-L266):元素标签名;文本节点返回 "#text"
  • textL268-L277):元素的直接文本内容(非递归),返回 TextHandler。若元素无直接文本则返回空字符串。
  • attribL333-L340):元素属性,返回只读的 AttributesHandlerdict 的只读子类)。支持 attrib['class']attrib.get('class'),也可用 article['class']'class' in article 语法(后者由 __getitem__/__contains__ 实现,见 L183-L191)。
  • html_contentL342-L350):元素内部 HTML 代码,使用 lxml.etree.tostring(method="html", with_tail=False) 序列化。
  • bodyL352-L357):返回当前 Selector 的原始未处理内容,对二进制/非 HTML 请求有用。
  • prettify()L359-L372):返回元素内部 HTML 的格式化版本。

textget_all_text 的区别:text 只取直接文本,而 get_all_text 递归收集所有子文本:

page.get_all_text()            # 全页面递归文本
article.get_all_text()         # 单个元素递归文本
article.text                   # 若该元素无直接文本则为 ''

get_all_text 参数

get_all_text 定义在 scrapling/parser.py,参数如下:

参数 默认值 说明
separator "\n" 各文本片段之间的连接符
strip False 拼接前是否对每段文本 strip()
ignore_tags ('script', 'style') 忽略的标签元组,连同其嵌套子元素一起排除
valid_values True True 时忽略空白/纯空格的文本内容

实现上会先用预编译的 XPath .//text()L61_find_all_text_nodes)拿到全部文本节点,再逐个判断其归属元素是否在被忽略集合内。

urljoin

urljoin(relative_url) 使用标准库 urllib.parse.urljoin 把构造时传入的 url 与相对链接拼成绝对 URL(L329-L331):

page.find_by_text('Tipping the Velvet').attrib['href']
# 'catalogue/tipping-the-velvet_999/index.html'
page.urljoin('catalogue/tipping-the-velvet_999/index.html')
# 'https://books.toscrape.com/catalogue/tipping-the-velvet_999/index.html'

DOM 树遍历

遍历类属性均返回 SelectorSelectors,并跳过注释等"非期望节点"(源码用 html_forbidden 过滤,见 L408-L413)。

  • parentL383-L387):直接父元素,无则 None,可链式调用 article.parent.parent.tag
  • childrenL397-L406):直接子元素列表。
  • below_elementsL389-L395):当前元素下所有后代(嵌套版 children),用预编译 XPath .//* 实现。
  • siblingsL408-L413):父元素的其它子元素(不含自身)。
  • next / previousL438-L460):兄弟顺序中的下一个/上一个,遇到注释节点会自动跳过。
  • iterancestors()L415-L420):生成器,从父元素起逐个产出所有祖先。
  • find_ancestor(func)L422-L430):遍历祖先直到某个满足传入函数(接收 Selector 返回 bool)的元素。
  • pathL432-L436):返回从根到当前元素的路径(Selectors)。
  • has_class(class_name)L374-L381):快速判断元素是否含某 class。
for ancestor in article.iterancestors():
    ...

article.find_ancestor(lambda a: a.has_class('product-list'))
article.find_ancestor(lambda a: a.css('.product-list'))  # 等价写法

childrenbelow_elements 的差异示例:对 .product-listchildren 只返回 3 个 <article>below_elements 则返回 <article> 及其全部子孙。

元素选择方法

css 与 xpath

css 定义在 scrapling/parser.pyxpath 定义在 L626-L694。两者签名一致:

def css(self, selector: str, identifier: str = "", adaptive: bool = False,
        auto_save: bool = False, percentage: int = 40) -> Selectors
def xpath(self, selector: str, identifier: str = "", adaptive: bool = False,
          auto_save: bool = False, percentage: int = 40, **kwargs) -> Selectors
  • selector:CSS3 选择器或 XPath 表达式。CSS 支持来自 cssselect 库,另实现了两个非标准伪元素:::text(选文本节点)与 ::attr(name)(选属性值)。
  • identifier:用于自适应存储/检索的字符串,不传则用 selector 本身。源码强烈建议:若计划换用不同 selector 但仍想定位同一元素,就传 identifier
  • adaptive:启用后若元素"之前保存过",则尝试重新定位。
  • auto_save:自动保存新元素供后续 adaptive 使用。
  • percentage:adaptive 定位时可接受的最低相似度百分比,默认 40,计算仅依赖页面结构,非特殊情况不建议改动。
  • xpath**kwargs 会作为 XPath 变量传入表达式。

实现要点:

  • css 内部通过 cssselect.parse 把 CSS 转成 XPath 再调 xpathL597-L604)。若 selector 含逗号且 adaptive 开启,会逐个拆分组合选择器再分别执行(L606-L619)。
  • 选择器语法错误会抛出 SelectorSyntaxError
  • adaptive/auto_save 被传但构造时未启用 adaptive,只会 log.warning 而非报错(L659-L684)。
page.css('.product')                          # 选所有 class=product
page.xpath('//*[@class="product"]')           # 同上(多 class 时不精确,推荐 CSS)
page.css('h1::text').get()                    # 取 h1 文本
page.css('a::attr(href)').get()               # 取 a 的 href
page.css('.product h1:contains("Phone")::text').get()
page.css('.product')[0].css('h1:contains("Phone")::text').get()  # 链式

注意://*[@class="product"] 在元素含多个 class 时并不精确,按类选择始终推荐 CSS。

find_all / find

find_allL696-L788)受 BeautifulSoup 的 find_all 启发,接受多类型过滤条件并做"瀑布式"链式过滤。findL790-L803)即 find_all 取第一个结果,无匹配返回 None

可接受的实参类型(*args):

类型 含义
str 标签名
list/tuple/set of str 标签名集合
dict[str, str] 属性名→属性值映射
re.Pattern 用内容正则过滤
Callable 自定义过滤函数(必须至少 1 个参数)

关键字参数 **kwargs 视为元素属性过滤。实现流程:先把标签+属性拼成 CSS 选择器执行(效率优先),再依次用正则与函数过滤结果集。

page.find_all('div', class_='quote')
page.find_all('div', {'class': 'quote'})
page.find_all({'class': 'quote'})
page.find_all('div', {'class': 'quote'}, lambda e: 'world' in e.css('.text::text').get())
page.find_all(lambda el: len(el.children) > 0)
page.find_all('span', re.compile(r'world'))
page.find_all(['div', 'span'], {'class': 'quote'})
page.find_all({'href$': 'Einstein'})        # href 以 Einstein 结尾
page.find_all({'href*': '/author/'})        # href 含 /author/

注意 scrapling/parser.py 中的 _whitelisted 字典:class_classfor_for,因为 class/for 是 Python 关键字不能直接作参数名。

find_by_text / find_by_regex

find_by_textL1094-L1140)与 find_by_regexL1160-L1197)按元素直接文本内容匹配,二者共享参数:

参数 默认 说明
first_match True True 返回首个匹配(单 Selector),否则返回 Selectors
partial False find_by_text:为 True 时做包含匹配而非精确匹配
case_sensitive False 是否区分大小写
clean_match True 匹配前是否压缩空白/连续空格
page.find_by_text('Tipping the Velvet')
page.find_by_text('the', partial=True, first_match=False)
page.find_by_text('the', partial=True, first_match=False, case_sensitive=True)
page.find_by_regex(r'£[\d\.]+')
import re
page.find_by_regex(re.compile(r'£[\d\.]+'))
page.find_by_regex(r'£[\d\.]+', first_match=False)

find_similar

find_similarL1013-L1072)受 AutoScraper 启发,找出与当前元素"同深度、同标签、同父标签、同祖父标签"且属性相似的兄弟元素。参数:

参数 默认 说明
similarity_threshold 0.2 属性相似度阈值,设 0 可关闭属性比对(步骤 3)
ignore_attributes ('href', 'src') 比对时忽略的属性名(URL 类属性不可靠)
match_text False 是否把文本内容纳入匹配计算

实现分三步:先用 XPath count(ancestor::*) 过滤出同深度、同 tag/parent/grandparent 路径的候选(L1052-L1059),再用 __are_alike 做属性模糊匹配(L970-L1011),属性完全一致的无属性元素计 100% 匹配。当前元素自身不会被包含在结果中

element = page.find_by_text('Tipping the Velvet')
similar = element.find_similar(ignore_attributes=['title'])
len(similar)
# 例如 19(页面上 20 个产品,去掉自身)
[e.attrib['href'] for e in similar]

选择器生成属性

来自 mixin SelectorsGenerationscrapling/core/mixins.py),Selector 通过继承直接获得 4 个属性:

  • generate_css_selector / generate_full_css_selector
  • generate_xpath_selector / generate_full_xpath_selector

实现(L15-L62):向上回溯父链,优先用元素的 id 作为"停靠点"生成短选择器;无 id 时则逐层用 tag+:nth-of-type(n)(CSS)或 [n](XPath)拼全路径。灵感来自 Firefox 的 CSS 选择器逻辑。

article.generate_css_selector          # 'body > div > article'
article.generate_full_css_selector
article.generate_xpath_selector        # '//body/div/article'
article.generate_full_xpath_selector

自适应(adaptive)相关方法

以下方法依赖构造时 adaptive=True,否则调用 save/retrieve 会抛 RuntimeErrorL879-L912)。

  • save(element, identifier)L879-L898):把元素(SelectorHtmlElement)的唯一特征存到存储,供日后检索/重定位;文本节点会退到其父元素。
  • retrieve(identifier)L900-L912):按 identifier 从存储取出特征字典,无则 None
  • relocate(element, percentage=40, selector_type=False)L507-L564):页面结构变化时重新搜索元素。把页面所有元素逐个与目标做相似度打分(__calculate_similarity_scoreL805-L870),取最高分且不低于 percentage 的元素集合。相似度从标签、文本、属性、class/id/href/src、路径、父元素、兄弟元素等多维计算。
# 先保存
page.save(page.css('.product')[0], 'first_product')
# 结构变化后检索并重新定位
data = page.retrieve('first_product')
found = page.relocate(data, percentage=40, selector_type=True)

文本操作:json / re / re_first

所有返回"字符串"的方法返回 TextHandlerstr 的子类),返回"字符串列表"的方法返回 TextHandlers。因此文本可继续链式 re/re_first/json/clean/sort

  • json()L915-L929):若响应可 JSON 化则返回对象,否则抛错。Selector 会优先用保存的原始 body 尝试解析,再回退到文本内容、最后 get_all_text
  • re(regex, replace_entities=True, clean_match=False, case_sensitive=True)L931-L945):对当前文本做正则,返回全部匹配的 TextHandlers
  • re_first(regex, default=None, ...)L947-L963):返回首个匹配(TextHandler),无则返回 default
page.css('.price_color')[0].re_first(r'[\d\.]+')     # '51.77'
page.css('.price_color').re(r'[\d\.]+')             # ['51.77', '53.74', ...]
page.css('.product_pod h3 a::attr(href)').re(r'catalogue/(.*)/index.html')
page.css('#page-data::text').get().json()
page.css('#page-data')[0].json()
# 原始 body 为 JSON 时
Selector('{"some_key": "some_value"}').json()
# {'some_key': 'some_value'}

replace_entities 默认开启(替换 HTML 实体引用),clean_match 默认关闭(开启则忽略空白匹配),case_sensitive 默认开启。

Scrapy 兼容的抽取方法

从 v0.4 起,SelectorSelectors 提供 get()/getall() 及其 Scrapy 别名 extract_first/extract(旧 get_all() 已移除)。定义见 scrapling/parser.py

extract = getall
extract_first = get
  • Selectorget() 返回 TextHandler(文本节点返回文本值,HTML 元素返回外层 HTML);getall() 返回含单条序列化字符串的 TextHandlers
  • Selectorsget(default=None) 返回首个元素序列化串或 defaultgetall() 返回全部序列化串。
page.css('h3')[0].get()            # '<h3>Product 1</h3>'
page.css('h3::text')[0].get()      # 'Product 1'
page.css('.price::text').get()     # '$10.99'
page.css('.price::text').getall()  # ['$10.99', '$20.99', '$15.99']
page.css('.price::text').get('')   # 带默认值

Selectors 类

Selectors 定义在 scrapling/parser.py,是内置 List[Selector] 的子类,__slots__ = (),共享全部 list 能力并扩展以下方法:

方法/属性 说明
xpath(selector, identifier, auto_save, percentage, **kwargs) 对每个元素执行 xpath 并展平结果(注意此处adaptive 参数,见 L1222-L1251
css(selector, identifier, auto_save, percentage) 同上,对 cssL1253-L1279
re(regex, ...) 对每个元素执行 re,合并为 TextHandlersL1281-L1297
re_first(regex, default, ...) 对每个元素执行 re,返回第一个有结果的,否则 defaultL1299-L1319
search(func) 返回首个满足函数的 Selector,否则 NoneL1321-L1329
filter(func) 返回所有满足函数的 SelectorsL1331-L1336
get(default=None) / getall() 抽取序列化文本(L1338-L1357
first / last 首/尾 Selector,空则 NoneL1359-L1367
length 等价 len(self)L1369-L1372

__getitem__ 做了类型收窄:整数下标返回 Selector,切片返回 SelectorsL1207-L1220)。

page.css('.product_pod').css('a')                       # 链式
page.css('.product_pod').search(lambda p: float(p.css('.price_color').re_first(r'[\d\.]+')) == 54.23)
page.css('.product_pod').filter(lambda p: float(p.css('.price_color').re_first(r'[\d\.]+')) > 50)
page.css('.product').first                               # 或 None
page.css('.product_pod').length

文本节点与可序列化性

从 v0.4 起,所有选择方法对文本节点/属性值也统一返回 Selector/Selectors(文本节点 tag"#text",其 text 属性即文本值,其余属性优雅返回空/默认值),详见 docs/parsing/main_classes.md

需要特别注意的是:SelectorSelectors 均不可 pickle 序列化__getstate__ 直接抛 TypeErrorscrapling/parser.pyL1374-L1376),这是 lxml 元素代理的固有限制。

向后兼容别名

文件末尾提供兼容旧版本命名的别名(scrapling/parser.py):

Adaptor = Selector
Adaptors = Selectors

总结与延伸阅读

本篇以 docs/api-reference/selector.md 引用的 Selector/Selectors 两个类为骨架,覆盖了构造参数、属性、DOM 遍历、CSS/XPath/过滤/文本/相似元素五种选择方式、自适应 save/retrieve/relocatejson/re/re_first 文本操作、Scrapy 兼容的 get/getall/extract,以及 Selectors 容器方法,全部对应到 scrapling/parser.py 的具体实现行。

相关延伸阅读:

登录后查看全文
热门项目推荐
相关项目推荐