readability-lxml 源码解析(一)

简介: readability-lxml 源码解析(一)

browser.py

def open_in_browser(html):
    """
    Open the HTML document in a web browser, saving it to a temporary
    file to open it.  Note that this does not delete the file after
    use.  This is mainly meant for debugging.
    """
    import os
    import webbrowser
    import tempfile
    # 创建 HTML 临时文件
    handle, fn = tempfile.mkstemp(suffix=".html")
    # 打开 HTML
    f = os.fdopen(handle, "wb")
    # 写入 HTML 文本
    try:
        f.write(b"<meta charset='UTF-8' />")
        f.write(html.encode("utf-8"))
    finally:
        # we leak the file itself here, but we should at least close it
        f.close()
    # 拼接文件的 URL
    url = "file://" + fn.replace(os.path.sep, "/")
    # 让浏览器打开
    webbrowser.open(url)
    return url

cleaner.py

# strip out a set of nuisance html attributes that can mess up rendering in RSS feeds
import re
from lxml.html.clean import Cleaner
# 不良属性
bad_attrs = ["width", "height", "style", "[-a-z]*color", "background[-a-z]*", "on*"]
# 匹配单引号包围的文本
single_quoted = "'[^']+'"
# 匹配双引号包围的文本
double_quoted = '"[^"]+"'
# 匹配非空格和标签结构字符
non_space = "[^ \"'>]+"
# 匹配带有不良属性的标签
htmlstrip = re.compile(
    "<"  # open
    "([^>]+) "  # prefix
    "(?:%s) *" % ("|".join(bad_attrs),)
    + "= *(?:%s|%s|%s)"  # undesirable attributes
    % (non_space, single_quoted, double_quoted)
    + "([^>]*)"  # value  # postfix
    ">",  # end
    re.I,
)
def clean_attributes(html):
    # 如果发现了不良属性
    while htmlstrip.search(html):
        # 那就把它移除
        html = htmlstrip.sub("<\\1\\2>", html)
    # 直到没有指定属性为止
    return html
def normalize_spaces(s):
    # 如果`s`为空返回空串
    if not s:
        return ""
    # 将连续的空白字符`\s+`替换为单个空格`\x20`并返回
    return " ".join(s.split())
# 调用 lxml 库的`Cleaner`创建标签格式化工具
html_cleaner = Cleaner(
    # 移除`<script>`标签
    scripts=True,
    # 移除`onXXX`属性
    javascript=True,
    # 移除注释节点
    comments=True,
    # 移除`<style>`标签`
    style=True,
    # 移除`<link>`标签
    links=True,
    # 不移除`<meta>`标签
    meta=False,
    # 不添加`nofollow`属性
    add_nofollow=False,
    # 不排版`<html> <head> <title>`
    page_structure=False,
    # 移除命令节点
    processing_instructions=True,
    # 不移除`<embed>`标签
    embedded=False,
    # 不溢出`<iframe>`标签
    frames=False,
    # 不移除`<form>`标签及控件标签
    forms=False,
    # 不移除'blink', 'marquee'标签
    annoying_tags=False,
    # 没有自定义的移除标签
    remove_tags=None,
    # 不移除未知标签
    remove_unknown_tags=False,
    # 不移除未知属性
    safe_attrs_only=False,
)

debug.py

import re
# FIXME: use with caution, can leak memory
uids = {}
uids_document = None
# 获取节点的描述文本
def describe_node(node):
    global uids
    if node is None:
        return ""
    # 如果节点没有名称
    # 返回占位符
    if not hasattr(node, "tag"):
        return "[%s]" % type(node)
    name = node.tag
    # 获取节点 ID 或者类名,转成选择器形式
    # 附加在名称之后
    if node.get("id", ""):
        name += "#" + node.get("id")
    if node.get("class", "").strip():
        name += "." + ".".join(node.get("class").split())
    # 如果节点是 DIV,并且具有 ID 或者类名
    # 从描述中移除 DIV
    if name[:4] in ["div#", "div."]:
        name = name[3:]
    # 如果名称是以下这四个
    if name in ["tr", "td", "div", "p"]:
        # 给节点分配一个自增的 UID,并缓存
        uid = uids.get(node)
        if uid is None:
            uid = uids[node] = len(uids) + 1
        # 在描述后面添加 UID
        name += "{%02d}" % uid
    return name
# 获取节点的描述文本,带有指定数量的父元素
def describe(node, depth=1):
    global uids, uids_document
    # 判断`uids_document`是否是根节点
    # 如果不是,清空`uids`和它
    doc = node.getroottree().getroot()
    if doc != uids_document:
        uids = {}
        uids_document = doc
    # return repr(NodeRepr(node))
    parent = ""
    # 判断深度是否为 0
    if depth and node.getparent() is not None:
        # 递归获取父元素的描述文本 
        parent = describe(node.getparent(), depth=depth - 1) + ">"
    # 将父元素描述和当前节点描述拼接
    return parent + describe_node(node)
RE_COLLAPSE_WHITESPACES = re.compile(r"\s+", re.U)
# 获取节点的简短内容
def text_content(elem, length=40):
    # 折叠空白字符,并移除所有 \r
    content = RE_COLLAPSE_WHITESPACES.sub(" ", elem.text_content().replace("\r", ""))
    # 如果内容长度小鱼限制,直接返回
    if len(content) < length:
        return content
    # 否则阶段并加省略号
    return content[:length] + "..."

encoding.py

import re
try:
    import cchardet
except ImportError:
    import chardet
import sys
# 匹配三个可能包含编码的标签
# `<meta charset>` `<meta content>` 和 `<?xml ?>`
RE_CHARSET = re.compile(r'<meta.*?charset=["\']*(.+?)["\'>]', flags=re.I)
RE_PRAGMA = re.compile(r'<meta.*?content=["\']*;?charset=(.+?)["\'>]', flags=re.I)
RE_XML = re.compile(r'^<\?xml.*?encoding=["\']*(.+?)["\'>]')
CHARSETS = {
    "big5": "big5hkscs",
    "gb2312": "gb18030",
    "ascii": "utf-8",
    "maccyrillic": "cp1251",
    "win1251": "cp1251",
    "win-1251": "cp1251",
    "windows-1251": "cp1251",
}
# 通过查表,将输入编码替换成它的超集
def fix_charset(encoding):
    """Overrides encoding when charset declaration
       or charset determination is a subset of a larger
       charset.  Created because of issues with Chinese websites"""
    encoding = encoding.lower()
    return CHARSETS.get(encoding, encoding)
def get_encoding(page):
    # Regex for XML and HTML Meta charset declaration
    # 获取所有包含编码的标签
    declared_encodings = (
        RE_CHARSET.findall(page) + RE_PRAGMA.findall(page) + RE_XML.findall(page)
    )
    # Try any declared encodings
    for declared_encoding in declared_encodings:
        try:
            # Python3 only
            # 如果是 Python3,将字节串转字符串
            if sys.version_info[0] == 3:
                # declared_encoding will actually be bytes but .decode() only
                # accepts `str` type. Decode blindly with ascii because no one should
                # ever use non-ascii characters in the name of an encoding.
                declared_encoding = declared_encoding.decode("ascii", "replace")
            encoding = fix_charset(declared_encoding)
            # Now let's decode the page
            page.decode(encoding)
            # It worked!
            return encoding
        except UnicodeDecodeError:
            pass
    # Fallback to chardet if declared encodings fail
    # Remove all HTML tags, and leave only text for chardet
    # 如果编码没有声明,尝试用 chardet 猜测
    # 移除所有标签
    text = re.sub(r'(\s*</?[^>]*>)+\s*', ' ', page).strip()
    # 如果长度小鱼 10,无法猜测,返回默认编码 UTF8
    enc = 'utf-8'
    if len(text) < 10:
        return enc  # can't guess
    # 猜测编码
    res = chardet.detect(text)
    # 如果猜测失败,设为 UTF8
    enc = res["encoding"] or "utf-8"
    # print '->', enc, "%.2f" % res['confidence']
    # 修复编码名称
    enc = fix_charset(enc)
    return enc
相关文章
|
2月前
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
269 29
|
2月前
|
前端开发 数据安全/隐私保护 CDN
二次元聚合短视频解析去水印系统源码
二次元聚合短视频解析去水印系统源码
78 4
|
2月前
|
JavaScript 算法 前端开发
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
2月前
|
移动开发 前端开发 JavaScript
从入门到精通:H5游戏源码开发技术全解析与未来趋势洞察
H5游戏凭借其跨平台、易传播和开发成本低的优势,近年来发展迅猛。接下来,让我们深入了解 H5 游戏源码开发的技术教程以及未来的发展趋势。
|
2月前
|
存储 前端开发 JavaScript
在线教育网课系统源码开发指南:功能设计与技术实现深度解析
在线教育网课系统是近年来发展迅猛的教育形式的核心载体,具备用户管理、课程管理、教学互动、学习评估等功能。本文从功能和技术两方面解析其源码开发,涵盖前端(HTML5、CSS3、JavaScript等)、后端(Java、Python等)、流媒体及云计算技术,并强调安全性、稳定性和用户体验的重要性。
|
2月前
|
负载均衡 JavaScript 前端开发
分片上传技术全解析:原理、优势与应用(含简单实现源码)
分片上传通过将大文件分割成多个小的片段或块,然后并行或顺序地上传这些片段,从而提高上传效率和可靠性,特别适用于大文件的上传场景,尤其是在网络环境不佳时,分片上传能有效提高上传体验。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
3月前
|
机器学习/深度学习 自然语言处理 算法
生成式 AI 大语言模型(LLMs)核心算法及源码解析:预训练篇
生成式 AI 大语言模型(LLMs)核心算法及源码解析:预训练篇
308 0
|
6月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
184 2
|
5月前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是"将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。创建型模式分为5种:单例模式、工厂方法模式抽象工厂式、原型模式、建造者模式。
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
5月前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析

推荐镜像

更多