readability-lxml 源码解析(二):`htmls.py`

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: readability-lxml 源码解析(二):`htmls.py`
from lxml.html import tostring
import lxml.html
import re
from .cleaners import normalize_spaces, clean_attributes
from .encoding import get_encoding
from .compat import str_
utf8_parser = lxml.html.HTMLParser(encoding="utf-8")
# 将 HTML 文本转为文档树
def build_doc(page):
    # 如果页面文本是字符串
    # 保持原样,不解析编码
    if isinstance(page, str_):
        encoding = None
        decoded_page = page
    else:
        # 否则获取其编码,默认 UTF8
        # 将字节串转化为字符串
        encoding = get_encoding(page) or "utf-8"
        decoded_page = page.decode(encoding, "replace")
    # XXX: we have to do .decode and .encode even for utf-8 pages to remove bad characters
    doc = lxml.html.document_fromstring(
        decoded_page.encode("utf-8", "replace"), parser=utf8_parser
    )
    return doc, encoding
# JS风格的正则替换函数
def js_re(src, pattern, flags, repl):
    # 将替换字符串中的 $ 换成 \\ 然后再执行 re.sub
    return re.compile(pattern, flags).sub(src, repl.replace("$", "\\"))
# 规范化实体
# 将一些 Unicode 字符替换为等价 ASCII 字符
def normalize_entities(cur_title):
    entities = {
        u"\u2014": "-",
        u"\u2013": "-",
        u"—": "-",
        u"–": "-",
        u"\u00A0": " ",
        u"\u00AB": '"',
        u"\u00BB": '"',
        u""": '"',
    }
    for c, r in entities.items():
        if c in cur_title:
            cur_title = cur_title.replace(c, r)
    return cur_title
# 规范化标题 = 规范化实体+空白
def norm_title(title):
    return normalize_entities(normalize_spaces(title))
def get_title(doc):
    # 获取`<title>`节点
    title = doc.find(".//title")
    # 如果找不到或者没有内容,返回占位符
    if title is None or title.text is None or len(title.text) == 0:
        return "[no-title]"
    # 规范化标题并返回
    return norm_title(title.text)
# 获取作者
def get_author(doc):
    # 获取`<meta name='author'>`
    author = doc.find(".//meta[@name='author']")
    # 还是找不到或者内容为空,就返回占位符
    if author is None or 'content' not in author.keys() or \
       len(author.get('content')) == 0:
        return "[no-author]"
    # 返回`content`属性的值
    return author.get('content')
def add_match(collection, text, orig):
    text = norm_title(text)
    if len(text.split()) >= 2 and len(text) >= 15:
        if text.replace('"', "") in orig.replace('"', ""):
            collection.add(text)
# 正文中标题候选元素的一些 CSS 选择器
TITLE_CSS_HEURISTICS = [
    "#title",
    "#head",
    "#heading",
    ".pageTitle",
    ".news_title",
    ".title",
    ".head",
    ".heading",
    ".contentheading",
    ".small_header_red",
]
# 获取简短标题
def shorten_title(doc):
    # 寻找`<title>`节点
    title = doc.find(".//title")
    # 如果没有,或者没有文本,那么返回空串
    if title is None or title.text is None or len(title.text) == 0:
        return ""
    # 规范化标题
    title = orig = norm_title(title.text)
    # 创建标题候选集
    candidates = set()
    # 对于每个`<h1> <h2> <h3>` 
    for item in [".//h1", ".//h2", ".//h3"]:
        for e in list(doc.iterfind(item)):
            # 如果它有内容,就加入候选
            if e.text:
                add_match(candidates, e.text, orig)
            if e.text_content():
                add_match(candidates, e.text_content(), orig)
    # 对于每个标题候选元素
    for item in TITLE_CSS_HEURISTICS:
        for e in doc.cssselect(item):
            # 如果它有内容,就加入候选
            if e.text:
                add_match(candidates, e.text, orig)
            if e.text_content():
                add_match(candidates, e.text_content(), orig)
    if candidates:
       # 如果候选集不为空,取最长元素当做标题
       title = sorted(candidates, key=len)[-1]
    else:
        # 将文章标题和网站名称分开,类似
        # `<title>text title | site name</title>`
        for delimiter in [" | ", " - ", " :: ", " / "]:
            # 对于每个分隔符,判断是否包含在标题中
            if delimiter in title:
                # 使用分隔符分割标题
                parts = orig.split(delimiter)
                # 找出标题网站名称前面还是后面
                # 如果第一个元素每空格分成四段或者以上
                # 就取第一个元素当标题,反之就取最后一个
                if len(parts[0].split()) >= 4:
                    title = parts[0]
                    break
                elif len(parts[-1].split()) >= 4:
                    title = parts[-1]
                    break
        else:
            if ": " in title:
                parts = orig.split(": ")
                if len(parts[-1].split()) >= 4:
                    title = parts[-1]
                else:
                    title = orig.split(": ", 1)[1]
    if not 15 < len(title) < 150:
        return orig
    return title
# 获取整洁版的正文
# is it necessary? Cleaner from LXML is initialized correctly in cleaners.py
def get_body(doc):
    # 删除`<script>`、`<link>`和`<style>`
    for elem in doc.xpath(".//script | .//link | .//style"):
        elem.drop_tree()
    # 获取文档的`<body>`,如果没有就是文档的根元素,之后获取其 HTML
    # tostring() always return utf-8 encoded string
    # FIXME: isn't better to use tounicode?
    raw_html = tostring(doc.body or doc)
    # 如果是字节串转换为字符串
    if isinstance(raw_html, bytes):
        raw_html = raw_html.decode()
    # 把一些不良属性移除
    # 但是先删属性后转 HTML 比较好,这就很迷
    cleaned = clean_attributes(raw_html)
    try:
        # BeautifulSoup(cleaned) #FIXME do we really need to try loading it?
        return cleaned
    except Exception:  # FIXME find the equivalent lxml error
        # logging.error("cleansing broke html content: %s\n---------\n%s" % (raw_html, cleaned))
        return raw_html
相关文章
|
11天前
|
缓存 Java 程序员
Map - LinkedHashSet&Map源码解析
Map - LinkedHashSet&Map源码解析
29 0
|
11天前
|
算法 Java 容器
Map - HashSet & HashMap 源码解析
Map - HashSet & HashMap 源码解析
26 0
|
11天前
|
存储 Java C++
Collection-PriorityQueue源码解析
Collection-PriorityQueue源码解析
21 0
|
9天前
|
存储
让星星⭐月亮告诉你,HashMap的put方法源码解析及其中两种会触发扩容的场景(足够详尽,有问题欢迎指正~)
`HashMap`的`put`方法通过调用`putVal`实现,主要涉及两个场景下的扩容操作:1. 初始化时,链表数组的初始容量设为16,阈值设为12;2. 当存储的元素个数超过阈值时,链表数组的容量和阈值均翻倍。`putVal`方法处理键值对的插入,包括链表和红黑树的转换,确保高效的数据存取。
30 5
|
10天前
|
Java Spring
Spring底层架构源码解析(三)
Spring底层架构源码解析(三)
|
10天前
|
XML Java 数据格式
Spring底层架构源码解析(二)
Spring底层架构源码解析(二)
|
11天前
|
算法 Java 程序员
Map - TreeSet & TreeMap 源码解析
Map - TreeSet & TreeMap 源码解析
20 0
|
11天前
|
安全 Java 程序员
Collection-Stack&Queue源码解析
Collection-Stack&Queue源码解析
24 0
|
1月前
|
存储 缓存 Java
什么是线程池?从底层源码入手,深度解析线程池的工作原理
本文从底层源码入手,深度解析ThreadPoolExecutor底层源码,包括其核心字段、内部类和重要方法,另外对Executors工具类下的四种自带线程池源码进行解释。 阅读本文后,可以对线程池的工作原理、七大参数、生命周期、拒绝策略等内容拥有更深入的认识。
108 29
什么是线程池?从底层源码入手,深度解析线程池的工作原理
|
1月前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
312 37

推荐镜像

更多