完全指南:從 LxmlLinkExtractor 參數(shù)到源碼級(jí)解析原理)
Scrapy 鏈接提取器Link Extractors完全指南從 LxmlLinkExtractor 參數(shù)到源碼級(jí)解析原理【免費(fèi)下載鏈接】scrapyScrapy, a fast high-level web crawling scraping framework for Python.項(xiàng)目地址: https://gitcode.com/GitHub_Trending/sc/scrapy本文以 Scrapy 官方文檔docs/topics/link-extractors.rst為核心結(jié)合倉(cāng)庫(kù)源碼 scrapy/linkextractors/lxmlhtml.py、scrapy/link.py 與測(cè)試用例 tests/test_linkextractors.py系統(tǒng)講解鏈接提取器的定位、全部構(gòu)造參數(shù)、extract_links調(diào)用鏈與過(guò)濾規(guī)則的真實(shí)執(zhí)行順序幫助你在 Spider 和CrawlSpider中精確控制提取哪些鏈接、如何規(guī)范化、如何去重。1. 什么是鏈接提取器對(duì)象模型與核心職責(zé)鏈接提取器Link Extractor是 Scrapy 中從響應(yīng)中抽取鏈接的對(duì)象。官方文檔給出了兩個(gè)基本事實(shí)鏈接提取器的__init__方法接收一組配置決定哪些鏈接可以被提取其extract_links方法從一個(gè)Response對(duì)象返回一個(gè)Link對(duì)象列表匹配配置的鏈接。鏈接提取器最常見的使用場(chǎng)景是在CrawlSpider中通過(guò)一組Rule對(duì)象驅(qū)動(dòng)爬取同時(shí)它也完全可以在普通 Spider 中直接使用。官方文檔給出的標(biāo)準(zhǔn)用法是把提取器實(shí)例化為 Spider 類變量在回調(diào)中遍歷鏈接from scrapy.linkextractors import LinkExtractor class MySpider(spider.Spider): name my_spider link_extractor LinkExtractor() def parse(self, response): for link in self.link_extractor.extract_links(response): yield Request(link.url, callbackself.parse)這里L(fēng)inkExtractor并不是一個(gè)獨(dú)立類而是對(duì)scrapy.linkextractors.lxmlhtml.LxmlLinkExtractor的便捷別名。從源碼 scrapy/linkextractors/init.py 可以看到它只做了一次頂層重導(dǎo)出from scrapy.linkextractors.lxmlhtml import LxmlLinkExtractor as LinkExtractor __all__ [IGNORED_EXTENSIONS, LinkExtractor]CrawlSpider內(nèi)部同樣依賴這個(gè)默認(rèn)提取器在 scrapy/spiders/crawl.py 中模塊級(jí)變量_default_link_extractor LinkExtractor()在Rule未顯式傳入link_extractor時(shí)作為兜底self.link_extractor: LinkExtractor link_extractor or _default_link_extractor2. 鏈接對(duì)象 Linkextract_links 的返回結(jié)構(gòu)extract_links返回的是 scrapy/link.py 中定義的Link對(duì)象列表。每個(gè)Link有 4 個(gè)屬性源碼使用__slots__ [fragment, nofollow, text, url]屬性含義示例url鏈接所指向的絕對(duì) URL相對(duì)鏈接已被拼合https://example.com/nofollow.htmltext錨標(biāo)簽內(nèi)的文本內(nèi)容Dont follow this onefragmentURL 中#之后的部分foonofollow錨標(biāo)簽rel屬性是否含nofollowTrue/False文檔中的示意 HTML 為a hrefhttps://example.com/nofollow.html#foo relnofollowDont follow this one/aLink實(shí)現(xiàn)了__eq__與__hash__比較/哈希均基于四個(gè)屬性——這正是CrawlSpider能夠在多條 Rule 之間去重_requests_to_follow中的seen: set[Link]的基礎(chǔ)。3. LxmlLinkExtractor 全參數(shù)詳解LxmlLinkExtractor的__init__簽名源碼 scrapy/linkextractors/lxmlhtml.py 第 305-323 行決定了提取行為逐參數(shù)說(shuō)明如下參數(shù)類型默認(rèn)值作用allow正則字符串或正則列表()絕對(duì) URL 必須匹配才提取為空則不過(guò)濾deny正則字符串或正則列表()URL 匹配則不提取優(yōu)先級(jí)高于allowallow_domains域名或域名列表()只提取來(lái)自這些域名的鏈接deny_domains域名或域名列表()排除這些域名的鏈接restrict_xpathsXPath 或 XPath 列表()只在這些 XPath 選中的區(qū)域內(nèi)掃描鏈接restrict_cssCSS 選擇器或列表()同restrict_xpaths在構(gòu)造時(shí)轉(zhuǎn)成 XPathrestrict_text正則或正則列表None鏈接文本必須匹配才提取tags標(biāo)簽或標(biāo)簽列表(a, area)掃描哪些標(biāo)簽*表示所有標(biāo)簽attrs屬性或?qū)傩粤斜?href,)在指定標(biāo)簽中掃描哪些屬性*表示所有屬性deny_tags標(biāo)簽或列表()排除的標(biāo)簽優(yōu)先級(jí)高于tags可與tags*組合2.17.0 新增deny_attrs屬性或列表()排除的屬性優(yōu)先級(jí)高于attrs2.17.0 新增canonicalizeboolFalse用w3lib.url.canonicalize_url規(guī)范化每個(gè) URLuniqueboolTrue是否對(duì)提取結(jié)果去重process_value可調(diào)用對(duì)象None等價(jià)lambda x: x轉(zhuǎn)換/改寫提取到的屬性值返回None丟棄該鏈接deny_extensions擴(kuò)展名或列表None即內(nèi)置IGNORED_EXTENSIONS排除這些擴(kuò)展名的鏈接stripboolTrue是否去除屬性值首尾空白幾個(gè)參數(shù)的關(guān)鍵行為可以從源碼中得到印證3.1 tags / attrs 與 deny_tags / deny_attrs 的匹配邏輯tags/attrs/deny_tags/deny_attrs最終被編譯成一個(gè)判定函數(shù)_name_matchesdef _name_matches(allowed: set[str], denied: set[str], name: str) - bool: if name in denied: return False return * in allowed or name in allowed即denied 集合優(yōu)先allowed 集合含*通配符則匹配任意名字。deny_tags默認(rèn)空元組不排除任何標(biāo)簽所以tags*deny_tags(script,)是提取一切標(biāo)簽的鏈接、但排除 script的標(biāo)準(zhǔn)組合。3.2 deny_extensions 的默認(rèn)值 IGNORED_EXTENSIONS不傳deny_extensions時(shí)默認(rèn)使用 scrapy/linkextractors/init.py 中定義的IGNORED_EXTENSIONS覆蓋五類資源壓縮包7z、bz2、rar、tar.gz、zip等圖片gif、jpg、png、svg、webp等音頻mp3、wav、flac系外的常見格式視頻mp4、avi、webm等Office 及其他xls/xlsx、doc/docx、pdf、css、js、py、exe等。匹配邏輯在 scrapy/utils/url.py 的url_has_any_extension取 URL path 小寫后逐個(gè)endswith判斷。注意構(gòu)造器會(huì)給每個(gè)擴(kuò)展名補(bǔ)前導(dǎo)點(diǎn). e所以deny_extensionspdf與deny_extensions.pdf效果一致。若你只想提取文件鏈接可顯式傳deny_extensions()關(guān)閉該過(guò)濾。3.3 canonicalize 與 unique 的語(yǔ)義差異文檔特別警示canonicalizeTrue時(shí) URL 經(jīng)canonicalize_url處理而canonicalize_url是為去重設(shè)計(jì)的它會(huì)改變服務(wù)端可見的 URL如去默認(rèn)端口、去 fragment 差異等因此若用提取器跟隨鏈接保持默認(rèn)canonicalizeFalse更穩(wěn)健。去重鍵的選擇也與此聯(lián)動(dòng)源碼_canonicalize_link_urldef _canonicalize_link_url(link: Link) - str: return canonicalize_url(link.url, keep_fragmentsTrue)canonicalizeFalse默認(rèn)去重鍵為保留 fragment的規(guī)范 URLsample3.html與sample3.html#foo視為不同鏈接canonicalizeTrue去重鍵直接取link.url此時(shí) URL 已被規(guī)范化且 fragment 被去掉兩者視為相同。測(cè)試用例test_extract_filter_allow_with_duplicates_canonicalizetests/test_linkextractors.py 第 77-95 行正驗(yàn)證了這一點(diǎn)uniqueFalse, canonicalizeTrue時(shí)重復(fù)鏈接全部保留且?guī)?fragment 的版本 URL 被規(guī)范化為無(wú) fragment 形式。3.4 process_value在過(guò)濾之前改寫值process_value接收從標(biāo)簽/屬性中掃描到的原始值可以修改并返回新值或返回None丟棄該鏈接。文檔給出的經(jīng)典例子是提取 JS 跳轉(zhuǎn)里的目標(biāo)地址頁(yè)面 HTMLa hrefjavascript:goToPage(../other/page.html); return falseLink text/a提取器配置import re from scrapy.linkextractors import LinkExtractor le LinkExtractor( process_valuelambda value: ( re.search(rjavascript:goToPage\((.*?), value).group(1) if re.search(rjavascript:goToPage\((.*?), value) else None ) )執(zhí)行時(shí)機(jī)很關(guān)鍵文檔原文強(qiáng)調(diào)process_value在allow/deny等過(guò)濾參數(shù)之前被調(diào)用過(guò)濾參數(shù)匹配的是它返回的值。若你想基于最終 URL 丟棄鏈接應(yīng)改用CrawlSpider的Rule.process_links它只會(huì)收到經(jīng)過(guò)過(guò)濾后保留下來(lái)的鏈接。3.5 strip為什么默認(rèn)去空白按 HTML5 標(biāo)準(zhǔn)a、area、img、iframe等元素的href/src屬性的首尾空白必須被剝離因此提取器默認(rèn)stripTrue使用 w3lib 的strip_html5_whitespace處理。測(cè)試數(shù)據(jù) tests/sample_data/link_extractor/linkextractor.html 中有一條a hrefpage 4.html提取結(jié)果為http://example.com/page%204.html——首尾空白被去除、空格被轉(zhuǎn)義而test_strip_false則驗(yàn)證了stripFalse時(shí)空白被保留并觸發(fā) URL 拼接異常被跳過(guò)或按原樣處理的路徑。4. 提取流程剖析extract_links 到底做了什么LxmlLinkExtractor.extract_links(response)的執(zhí)行鏈源碼第 405-428 行可以概括為五步解析 base URLget_base_url(response)從頁(yè)面base href標(biāo)簽提取基準(zhǔn)地址圈定掃描區(qū)域若配置了restrict_xpaths含restrict_css轉(zhuǎn)換后的 XPath只對(duì)response.xpath(x)命中的子文檔逐段提取否則對(duì)整個(gè)response.selector提取逐節(jié)點(diǎn)提取底層LxmlParserLinkExtractor._extract_links遍歷 lxml 文檔元素document.iter(etree.Element)按scan_tag/scan_attr謂詞篩選標(biāo)簽與屬性對(duì)每個(gè)屬性值strip_html5_whitespace若stripTrue→urljoin(base_url, attr_val)拼成絕對(duì) URL →process_value改寫 →safe_url_string按響應(yīng)編碼安全化 → 再urljoin(response_url, url)兜底修正相對(duì)鏈接最后用 XPathstring()取錨內(nèi)文本并根據(jù)rel屬性解析nofollow構(gòu)造Link對(duì)象過(guò)濾與規(guī)范化_process_links先按_link_allowed過(guò)濾順序見第 5 節(jié)再按canonicalize決定是否規(guī)范化 URL最后委托底層去重全局去重若uniqueTrue對(duì)跨區(qū)域匯總的結(jié)果做最終unique_list鍵為 3.3 節(jié)所述的去重函數(shù)。幾個(gè)值得注意的實(shí)現(xiàn)細(xì)節(jié)無(wú)效 scheme 直接丟棄_is_valid_url只接受http、https、file、ftp四種 schemescrapy/linkextractors/init.py 第 123-124 行javascript:鏈接除非被process_value轉(zhuǎn)成真實(shí) URL否則不會(huì)進(jìn)入結(jié)果壞 URL 靜默跳過(guò)safe_url_string拋ValueError時(shí)僅記 debug 日志并continue不會(huì)中斷整個(gè)頁(yè)面提取XHTML 命名空間透明處理_nons輔助函數(shù)會(huì)剝掉http://www.w3.org/1999/xhtml前綴因此 XHTML 頁(yè)面中a也能被tags(a,)命中restrict_css與restrict_xpaths可疊加構(gòu)造器中 CSS 選擇器經(jīng)parsel.csstranslator.HTMLTranslator轉(zhuǎn) XPath 后追加到restrict_xpaths元組測(cè)試test_restrict_css_and_restrict_xpaths_together驗(yàn)證了兩者可同時(shí)生效。5. 過(guò)濾規(guī)則的執(zhí)行順序matches() 與 _link_allowed()過(guò)濾分兩層。第一層是matches(url)方法源碼第 381-393 行只處理域名與正則供需要單獨(dú)判斷 URL 的場(chǎng)景復(fù)用def matches(self, url: str) - bool: if self.allow_domains and not url_is_from_any_domain(url, self.allow_domains): return False if self.deny_domains and url_is_from_any_domain(url, self.deny_domains): return False allowed ((regex.search(url) for regex in self.allow_res) if self.allow_res else [True]) denied (regex.search(url) for regex in self.deny_res) if self.deny_res else () return any(allowed) and not any(denied)第二層是_link_allowed(link)第 361-379 行完整判定順序?yàn)開is_valid_url——scheme 必須是 http/https/file/ftp否則拒絕allow正則——配置了則必須命中其一deny正則——命中即拒絕deny 優(yōu)先于 allowallow_domains——配置了則 URL 主機(jī)必須屬于該域精確匹配或子域見url_is_from_any_domain的endswith(f.cpe4elpci5g)邏輯deny_domains——命中即拒絕deny_extensions——path 以任一被禁擴(kuò)展名結(jié)尾即拒絕restrict_text——配置了則鏈接text必須匹配其一。注意正則用的是re.search而非re.match_matches函數(shù)即子串命中也算匹配寫allow規(guī)則時(shí)應(yīng)自行考慮加邊界如^/$。6. 典型配置示例可直接運(yùn)行以下示例以官方測(cè)試數(shù)據(jù)頁(yè)為參照該頁(yè)含相對(duì)鏈接、重復(fù)鏈接、帶 fragment 鏈接、跨域鏈接與空白 hreffrom scrapy.linkextractors import LinkExtractor # 1) 默認(rèn)行為提取 a/area 的 href按 IGNORED_EXTENSIONS 排除文件鏈接 # 相對(duì)鏈接拼成絕對(duì) URL并按 URL 去重 le LinkExtractor() # 2) 只用 URL 正則過(guò)濾search 語(yǔ)義子串即可命中 le LinkExtractor(allowr^https?://example\.com/sample\d\.html$) # 3) allow deny 組合deny 優(yōu)先 le LinkExtractor(allowsample, deny3) # 4) 域名白/黑名單 le LinkExtractor(allow_domainsexample.com) le LinkExtractor(deny_domains[example.com]) # 5) 限定頁(yè)面區(qū)域XPath 與 CSS 二選一或疊加 le LinkExtractor(restrict_xpaths//div[idwrapper]) le LinkExtractor(restrict_css#wrapper a) # 6) 按鏈接文本過(guò)濾正則 le LinkExtractor(restrict_textr^sample \d) # 7) 擴(kuò)大掃描面所有標(biāo)簽的所有屬性僅排除 script le LinkExtractor(tags*, attrs*, deny_tagsscript) # 8) 提取文件鏈接關(guān)閉擴(kuò)展名過(guò)濾只保留 pdf le LinkExtractor(deny_extensions[]) # 注意完全關(guān)閉 # 9) 不去重保留重復(fù)鏈接如頁(yè)面內(nèi)重復(fù)出現(xiàn)的入口 le LinkExtractor(uniqueFalse) # 10) 跟隨鏈接場(chǎng)景下推薦保持 canonicalizeFalse默認(rèn) # 僅在純?nèi)ブ亟y(tǒng)計(jì)場(chǎng)景考慮 canonicalizeTrue le LinkExtractor(canonicalizeTrue, uniqueFalse)7. 與 CrawlSpider 的協(xié)作Rule 如何消費(fèi)提取結(jié)果在 scrapy/spiders/crawl.py 的_requests_to_follow中每條 Rule 的執(zhí)行路徑是for rule_index, rule in enumerate(self._rules): links [lnk for lnk in rule.link_extractor.extract_links(response) if lnk not in seen] for link in rule.process_links(links): seen.add(link) request self._build_request(rule_index, link) yield rule.process_request(request, response)要點(diǎn)每條 Rule 的提取結(jié)果先與seen集合做Link級(jí)去重跨 Rule 不會(huì)重復(fù)請(qǐng)求同一鏈接這依賴Link.__hash__生成的Request的meta里寫入ruleRule 序號(hào)與link_text回調(diào)self._callback據(jù)此還原是哪條 Rule 命中實(shí)現(xiàn)一條 Rule 一個(gè)回調(diào)的分發(fā)Rule(process_links...)是文檔推薦的按最終 URL 丟棄鏈接的位置——它只接收allow/deny等參數(shù)保留下來(lái)的鏈接晚于提取器自身的過(guò)濾響應(yīng)若非HtmlResponse如 JSON/XML_requests_to_follow直接返回不產(chǎn)生跟隨請(qǐng)求。8. 驗(yàn)證行為用倉(cāng)庫(kù)測(cè)試與樣例數(shù)據(jù)自檢tests/sample_data/link_extractor/linkextractor.html官方測(cè)試頁(yè)含base href、area、重復(fù)鏈接、fragment 鏈接、空白 href是復(fù)現(xiàn)提取行為的最小樣本tests/test_linkextractors.py覆蓋test_extract_all_links默認(rèn)行為、test_extract_filter_allow_and_denydeny 優(yōu)先、test_nofollowrel 解析、test_restrict_css/test_restrict_css_and_restrict_xpaths_together區(qū)域限定、test_process_value值改寫、test_strip_false空白處理等tests/sample_data/link_extractor/ 下另有l(wèi)inkextractor_latin1.html、linkextractor_noenc.html等編碼邊界樣本驗(yàn)證非 UTF-8 頁(yè)面的safe_url_string路徑。本地復(fù)現(xiàn)任一用例的最簡(jiǎn)方式在安裝了本倉(cāng)庫(kù)的開發(fā)環(huán)境python -m pytest tests/test_linkextractors.py -k extract_all_links -v9. 小結(jié)與實(shí)用建議默認(rèn)值即最佳起點(diǎn)LxmlLinkExtractor()的默認(rèn)組合a/areahref 文件擴(kuò)展名過(guò)濾 URL 去重 空白剝離已能覆蓋絕大多數(shù)跟隨場(chǎng)景按需疊加allow/restrict_css即可寫正則前先想清楚執(zhí)行順序process_value→allow→deny→ 域名 → 擴(kuò)展名 →restrict_text且正則均為search子串匹配去重鍵與規(guī)范化聯(lián)動(dòng)需要區(qū)分 fragment 差異時(shí)用默認(rèn)canonicalizeFalse做 URL 指紋統(tǒng)計(jì)時(shí)才考慮canonicalizeTrue在 CrawlSpider 里控制最終取舍提取器參數(shù)管提什么Rule.process_links管留什么兩者分層使用比把一切塞進(jìn)正則更可維護(hù)。【免費(fèi)下載鏈接】scrapyScrapy, a fast high-level web crawling scraping framework for Python.項(xiàng)目地址: https://gitcode.com/GitHub_Trending/sc/scrapy創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考