Source code for langchain_community.document_loaders.sitemap

import itertools
import re
from typing import Any, Callable, Generator, Iterable, Iterator, List, Optional, Tuple
from urllib.parse import urlparse

from langchain_core.documents import Document

from langchain_community.document_loaders.web_base import WebBaseLoader


def _default_parsing_function(content: Any) -> str:
    return str(content.get_text())


def _default_meta_function(meta: dict, _content: Any) -> dict:
    return {"source": meta["loc"], **meta}


def _batch_block(iterable: Iterable, size: int) -> Generator[List[dict], None, None]:
    it = iter(iterable)
    while item := list(itertools.islice(it, size)):
        yield item


def _extract_scheme_and_domain(url: str) -> Tuple[str, str]:
    """从给定的URL中提取方案(scheme)+域名(domain)。

参数:
    url(str):输入的URL。

返回:
    返回一个包含方案和域名的2元组。
"""
    parsed_uri = urlparse(url)
    return parsed_uri.scheme, parsed_uri.netloc


[docs]class SitemapLoader(WebBaseLoader): """加载站点地图及其URL。 **安全提示** :此加载器可用于加载站点地图中指定的所有URL。 如果恶意行为者获取了站点地图的访问权限,他们可以通过修改站点地图来强制 服务器加载其他域中的URL。这可能导致服务器端请求伪造(SSRF)攻击;例如, 攻击者可以强制服务器加载不公开访问的内部服务端点的URL。虽然攻击者 可能无法立即访问这些数据,但这些数据可能泄漏 到下游系统中(例如,数据加载器用于为索引加载数据)。 此加载器是一个网络爬虫,通常不应将网络爬虫部署 具有对任何内部服务器的网络访问权限。 控制谁可以提交爬取请求以及爬虫具有什么网络访问权限。 默认情况下,如果站点地图不是本地文件,则加载器将仅加载与站点地图相同域中的URL。 如果将 restrict_to_same_domain 设置为 False(不推荐),则可以禁用此功能。 如果站点地图是本地文件,则默认情况下不会应用此类风险缓解措施。 使用 filter URLs 参数来限制可以加载的URL。 请参阅 https://python.langchain.com/docs/security"""
[docs] def __init__( self, web_path: str, filter_urls: Optional[List[str]] = None, parsing_function: Optional[Callable] = None, blocksize: Optional[int] = None, blocknum: int = 0, meta_function: Optional[Callable] = None, is_local: bool = False, continue_on_failure: bool = False, restrict_to_same_domain: bool = True, **kwargs: Any, ): """使用网页路径和可选的过滤URL进行初始化。 参数: web_path:网站地图的URL。也可以是本地路径。 filter_urls:正则表达式列表。如果指定,只有匹配其中一个过滤URL的URL将被加载。 *警告* 过滤URL将被解释为正则表达式。如果不希望其被解释为正则表达式语法,请记得转义特殊字符。例如,`.` 经常出现在URL中,如果要匹配字面上的 `.` 而不是任何字符,则应该转义它。 当 restrict_to_same_domain 为 True 且网站地图不是本地文件时,restrict_to_same_domain 优先于 filter_urls。 parsing_function:用于解析 bs4.Soup 输出的函数。 blocksize:每个块中的站点地图位置数。 blocknum:应加载的块的编号 - 从零开始计数。默认值:0 meta_function:用于解析 bs4.Soup 输出的元数据的函数。 当设置此方法时,还要记得将 metadata["loc"] 复制到 metadata["source"],如果正在使用此字段。 is_local:站点地图是否为本地文件。默认值:False continue_on_failure:是否在加载URL时发生错误时继续加载站点地图,发出警告而不是引发异常。将此设置为 True 可使加载器更加健壮,但也可能导致数据丢失。默认值:False restrict_to_same_domain:是否将加载限制为与站点地图相同域的URL。注意:仅当站点地图不是本地文件时才会应用此选项! """ if blocksize is not None and blocksize < 1: raise ValueError("Sitemap blocksize should be at least 1") if blocknum < 0: raise ValueError("Sitemap blocknum can not be lower then 0") try: import lxml # noqa:F401 except ImportError: raise ImportError( "lxml package not found, please install it with `pip install lxml`" ) super().__init__(web_paths=[web_path], **kwargs) # Define a list of URL patterns (interpreted as regular expressions) that # will be allowed to be loaded. # restrict_to_same_domain takes precedence over filter_urls when # restrict_to_same_domain is True and the sitemap is not a local file. self.allow_url_patterns = filter_urls self.restrict_to_same_domain = restrict_to_same_domain self.parsing_function = parsing_function or _default_parsing_function self.meta_function = meta_function or _default_meta_function self.blocksize = blocksize self.blocknum = blocknum self.is_local = is_local self.continue_on_failure = continue_on_failure
[docs] def parse_sitemap(self, soup: Any) -> List[dict]: """解析站点地图xml并加载到字典列表中。 参数: soup:BeautifulSoup对象。 返回: 字典列表。 """ els = [] for url in soup.find_all("url"): loc = url.find("loc") if not loc: continue # Strip leading and trailing whitespace and newlines loc_text = loc.text.strip() if self.restrict_to_same_domain and not self.is_local: if _extract_scheme_and_domain(loc_text) != _extract_scheme_and_domain( self.web_path ): continue if self.allow_url_patterns and not any( re.match(regexp_pattern, loc_text) for regexp_pattern in self.allow_url_patterns ): continue els.append( { tag: prop.text for tag in ["loc", "lastmod", "changefreq", "priority"] if (prop := url.find(tag)) } ) for sitemap in soup.find_all("sitemap"): loc = sitemap.find("loc") if not loc: continue soup_child = self.scrape_all([loc.text], "xml")[0] els.extend(self.parse_sitemap(soup_child)) return els
[docs] def lazy_load(self) -> Iterator[Document]: """加载站点地图。""" if self.is_local: try: import bs4 except ImportError: raise ImportError( "beautifulsoup4 package not found, please install it" " with `pip install beautifulsoup4`" ) fp = open(self.web_path) soup = bs4.BeautifulSoup(fp, "xml") else: soup = self._scrape(self.web_path, parser="xml") els = self.parse_sitemap(soup) if self.blocksize is not None: elblocks = list(_batch_block(els, self.blocksize)) blockcount = len(elblocks) if blockcount - 1 < self.blocknum: raise ValueError( "Selected sitemap does not contain enough blocks for given blocknum" ) else: els = elblocks[self.blocknum] results = self.scrape_all([el["loc"].strip() for el in els if "loc" in el]) for i, result in enumerate(results): yield Document( page_content=self.parsing_function(result), metadata=self.meta_function(els[i], result), )