Files
AI/참고/trafilatura-master/tests/metadata_tests.py
2026-05-12 19:40:31 +09:00

419 lines
22 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Unit tests for the metadata parts.
"""
import logging
import sys
from lxml import html
from lxml.etree import XPath
from trafilatura.json_metadata import normalize_authors, normalize_json
from trafilatura.metadata import check_authors, extract_metadata, extract_metainfo, extract_url, normalize_tags
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
def test_titles():
'''Test the extraction of titles'''
tests = [
('<html><body><h3 class="title">T</h3><h3 id="title"></h3></body></html>', None),
('<html><head><title>Test Title</title><meta property="og:title" content=" " /></head><body><h1>First</h1></body></html>', 'First'),
('<html><head><title>Test Title</title><meta name="title" content=" " /></head><body><h1>First</h1></body></html>', 'First'),
('<html><head><title>Test Title</title></head><body></body></html>', 'Test Title'),
('<html><body><h1>First</h1><h1>Second</h1></body></html>', 'First'),
('<html><body><h1> </h1><div class="post-title">Test Title</div></body></html>', 'Test Title'),
('<html><body><h2 class="block-title">Main menu</h2><h1 class="article-title">Test Title</h1></body></html>', 'Test Title'),
('<html><body><h2>First</h2><h1>Second</h1></body></html>', 'Second'),
('<html><body><h2>First</h2><h2>Second</h2></body></html>', 'First'),
('<html><body><title></title></body></html>', None)
]
for doc, expected_title in tests:
metadata = extract_metadata(doc)
assert metadata.title == expected_title
metadata = extract_metadata(r'''<html><body><script type="application/ld+json">{"@context":"https:\/\/schema.org","@type":"Article","name":"Semantic satiation","url":"https:\/\/en.wikipedia.org\/wiki\/Semantic_satiation","sameAs":"http:\/\/www.wikidata.org\/entity\/Q226007","mainEntity":"http:\/\/www.wikidata.org\/entity\/Q226007","author":{"@type":"Organization","name":"Contributors to Wikimedia projects"},"publisher":{"@type":"Organization","name":"Wikimedia Foundation, Inc.","logo":{"@type":"ImageObject","url":"https:\/\/www.wikimedia.org\/static\/images\/wmf-hor-googpub.png"}},"datePublished":"2006-07-12T09:27:14Z","dateModified":"2020-08-31T23:55:26Z","headline":"psychological phenomenon in which repetition causes a word to temporarily lose meaning for the listener"}</script>
<script>(RLQ=window.RLQ||[]).push(function(){mw.config.set({"wgBackendResponseTime":112,"wgHostname":"mw2373"});});</script></html>''')
assert metadata.title == 'Semantic satiation'
metadata = extract_metadata('<html><head><title> - Home</title></head><body/></html>')
assert metadata.title == '- Home'
metadata = extract_metadata('<html><head><title>My Title » My Website</title></head><body/></html>')
assert metadata.title == "My Title" # TODO: and metadata.sitename == "My Website"
def test_authors():
'''Test the extraction of author names'''
# normalization
assert normalize_authors(None, 'abc') == 'Abc'
assert normalize_authors(None, 'Steve Steve 123') == 'Steve Steve'
assert normalize_authors(None, 'By Steve Steve') == 'Steve Steve'
assert normalize_json('Test \\nthis') == 'Test this'
assert normalize_json('\\uD800\\uDC00Hello\\uDBFF\\uDFFF') == 'Hello'
assert normalize_json('Test \u3010ABC\u3011') == 'Test 【ABC】'
assert normalize_json("Seán Federico O'Murchú") == "Seán Federico O'Murchú"
# blacklist
metadata = extract_metadata('<html><head><meta itemprop="author" content="Jenny Smith"/></head><body></body></html>', author_blacklist={'Jenny Smith'})
assert metadata.author is None
# extraction
begin, end = '<html><head>', '</head><body></body></html>'
htmldocs = [
f'{begin}<meta itemprop="author" content="Jenny Smith"/>{end}',
f'{begin}<meta itemprop="author" content="Jenny Smith"/><meta itemprop="author" content="John Smith"/>{end}',
f'{begin}<meta itemprop="author" content="Jenny Smith und John Smith"/>{end}',
f'{begin}<meta name="author" content="Jenny Smith"/><meta name="author" content="John Smith"/>{end}',
f'{begin}<meta name="author" content="Jenny Smith and John Smith"/>{end}',
f'{begin}<meta name="author" content="Jenny Smith"/>{end}',
f'{begin}<meta name="author" content="Hank O&#39;Hop"/>{end}',
f'{begin}<meta name="author" content="Jenny Smith ❤️"/>{end}',
f'{begin}<meta name="citation_author" content="Jenny Smith and John Smith"/>{end}',
f'{begin}<meta property="author" content="Jenny Smith"/><meta property="author" content="John Smith"/>{end}',
f'{begin}<meta itemprop="author" content="Jenny Smith and John Smith"/>{end}',
f'{begin}<meta name="article:author" content="Jenny Smith"/>{end}',
]
expected_authors = [
'Jenny Smith',
'Jenny Smith; John Smith',
'Jenny Smith; John Smith',
'Jenny Smith; John Smith',
'Jenny Smith; John Smith',
'Jenny Smith',
'Hank O\'Hop',
'Jenny Smith',
'Jenny Smith; John Smith',
'Jenny Smith; John Smith',
'Jenny Smith; John Smith',
'Jenny Smith',
]
for doc, expected_author in zip(htmldocs, expected_authors):
metadata = extract_metadata(doc)
assert metadata.author == expected_author
begin, end = '<html><body>', '</body></html>'
htmldocs = [
f'{begin}<a href="" rel="author">Jenny Smith</a>{end}',
f'{begin}<a href="" rel="author">Jenny "The Author" Smith</a>{end}',
f'{begin}<span class="author">Jenny Smith</span>f{end}',
f'{begin}<h4 class="author">Jenny Smith</h4>f{end}',
f'{begin}<h4 class="author">Jenny Smith — Trafilatura</h4>f{end}',
f'{begin}<span class="wrapper--detail__writer">Jenny Smith</span>f{end}',
f'{begin}<span id="author-name">Jenny Smith</span>f{end}',
f'{begin}<figure data-component="Figure"><div class="author">Jenny Smith</div></figure>f{end}',
f'{begin}<div class="sidebar"><div class="author">Jenny Smith</div></figure>f{end}',
f'{begin}<div class="quote"><p>My quote here</p><p class="quote-author"><span>—</span> Jenny Smith</p></div>f{end}',
f'{begin}<span class="author">Jenny Smith and John Smith</span>f{end}',
f'{begin}<a class="author">Jenny Smith</a>f{end}',
f'{begin}<a class="author">Jenny Smith <div class="title">Editor</div></a>f{end}',
f'{begin}<a class="author">Jenny Smith from Trafilatura</a>f{end}',
f'{begin}<meta itemprop="author" content="Fake Author"/><a class="author">Jenny Smith from Trafilatura</a>f{end}',
f'{begin}<a class="username">Jenny Smith</a>f{end}',
f'{begin}<div class="submitted-by"><a>Jenny Smith</a></div>f{end}',
f'{begin}<div class="byline-content"><div class="byline"><a>Jenny Smith</a></div><time>July 12, 2021 08:05</time></div>f{end}',
f'{begin}<h3 itemprop="author">Jenny Smith</h3>f{end}',
f'{begin}<div class="article-meta article-meta-byline article-meta-with-photo article-meta-author-and-reviewer" itemprop="author" itemscope="" itemtype="http://schema.org/Person"><span class="article-meta-photo-wrap"><img src="" alt="Jenny Smith" itemprop="image" class="article-meta-photo"></span><span class="article-meta-contents"><span class="article-meta-author">By <a href="" itemprop="url"><span itemprop="name">Jenny Smith</span></a></span><span class="article-meta-date">May 18 2022</span><span class="article-meta-reviewer">Reviewed by <a href="">Robert Smith</a></span></span></div>f{end}',
f'{begin}<div data-component="Byline">Jenny Smith</div>f{end}',
f'{begin}<span id="author">Jenny Smith</span>f{end}',
f'{begin}<span id="author">Jenny Smith The Moon</span>f{end}',
f'{begin}<span id="author">Jenny_Smith</span>f{end}',
]
expected_authors = [
'Jenny Smith',
'Jenny Smith',
'Jenny Smith',
'Jenny Smith',
'Jenny Smith',
'Jenny Smith',
'Jenny Smith',
None,
None,
None,
'Jenny Smith; John Smith',
'Jenny Smith',
'Jenny Smith',
'Jenny Smith',
'Jenny Smith',
'Jenny Smith',
'Jenny Smith',
'Jenny Smith',
'Jenny Smith',
'Jenny Smith',
'Jenny Smith',
'Jenny Smith',
'Jenny Smith',
'Jenny Smith',
]
for doc, expected_author in zip(htmldocs, expected_authors):
metadata = extract_metadata(doc)
assert metadata.author == expected_author
htmldocs = [
f'{begin}<span itemprop="author name">Shannon Deery, Mitch Clarke, Susie OBrien, Laura Placella, Kara Irving, Jordy Atkinson, Suzan Delibasic</span>f{end}',
f'{begin}<address class="author">Jenny Smith</address>f{end}',
f'{begin}<author>Jenny Smith</author>f{end}',
'<html><head><meta data-rh="true" property="og:author" content="By &lt;a href=&quot;/profiles/amir-vera&quot;&gt;Amir Vera&lt;/a&gt;, Seán Federico O&#x27;Murchú, &lt;a href=&quot;/profiles/tara-subramaniam&quot;&gt;Tara Subramaniam&lt;/a&gt; and Adam Renton, CNN"/></head><body>f{end}',
f'{begin}<div class="author"><span class="profile__name"> Jenny Smith </span> <a href="https://twitter.com/jenny_smith" class="profile__social" target="_blank"> @jenny_smith </a> <span class="profile__extra lg:hidden"> 11:57AM </span> </div>f{end}',
f'{begin}<p class="author-section byline-plain">By <a class="author" rel="nofollow">Jenny Smith For Daily Mail Australia</a></p>f{end}',
f'{begin}<div class="o-Attribution__a-Author"><span class="o-Attribution__a-Author--Label">By:</span><span class="o-Attribution__a-Author--Prefix"><span class="o-Attribution__a-Name"><a href="//web.archive.org/web/20210707074846/https://www.discovery.com/profiles/ian-shive">Ian Shive</a></span></span></div>f{end}',
f'{begin}<div class="ArticlePage-authors"><div class="ArticlePage-authorName" itemprop="name"><span class="ArticlePage-authorBy">By&nbsp;</span><a aria-label="Ben Coxworth" href="https://newatlas.com/author/ben-coxworth/"><span>Ben Coxworth</span></a></div></div>f{end}',
f'{begin}<div><strong><a class="d1dba0c3091a3c30ebd6" data-testid="AuthorURL" href="/by/p535y1">AUTHOR NAME</a></strong></div>f{end}'
]
expected_authors = [
'Shannon Deery; Mitch Clarke; Susie OBrien; Laura Placella; Kara Irving; Jordy Atkinson; Suzan Delibasic',
'Jenny Smith',
'Jenny Smith',
"Amir Vera; Seán Federico O'Murchú; Tara Subramaniam; Adam Renton; CNN",
'Jenny Smith',
'Jenny Smith',
'Ian Shive',
'Ben Coxworth',
'AUTHOR NAME'
]
for doc, expected_author in zip(htmldocs, expected_authors):
metadata = extract_metadata(doc)
assert metadata.author == expected_author
# check authors string
blacklist = {"A", "b"}
assert check_authors("a; B; c; d", blacklist) == "c; d"
assert check_authors("a;B;c;d", blacklist) == "c; d"
def test_url():
'''Test URL extraction'''
htmldocs = [
'<html><head><meta property="og:url" content="https://example.org"/></head><body></body></html>',
'<html><head><link rel="canonical" href="https://example.org"/></head><body></body></html>',
'<html><head><meta name="twitter:url" content="https://example.org"/></head><body></body></html>',
'<html><head><link rel="alternate" hreflang="x-default" href="https://example.org"/></head><body></body></html>',
'<html><head><link rel="canonical" href="/article/medical-record"/></head><body></body></html>'
'<html><head><base href="https://example.org" target="_blank"/></head><body></body></html>',
]
default_urls = [None, None, None, None, "https://example.org", None]
expected_url = 'https://example.org'
for doc, default_url in zip(htmldocs, default_urls):
metadata = extract_metadata(doc, default_url)
assert metadata.url == expected_url
# test on partial URLs
url = extract_url(html.fromstring('<html><head><link rel="canonical" href="/article/medical-record"/><meta name="twitter:url" content="https://example.org"/></head><body></body></html>'))
assert url == 'https://example.org/article/medical-record'
def test_description():
'''Test the extraction of descriptions'''
metadata = extract_metadata('<html><head><meta itemprop="description" content="Description"/></head><body></body></html>')
assert metadata.description == 'Description'
metadata = extract_metadata('<html><head><meta property="og:description" content="&amp;#13; A Northern Territory action plan, which includes plans to support development and employment on Aboriginal land, has received an update. &amp;#13..." /></head><body></body></html>')
assert metadata.description == 'A Northern Territory action plan, which includes plans to support development and employment on Aboriginal land, has received an update. ...'
def test_dates():
'''Simple tests for date extraction (most of the tests are carried out externally for htmldate module)'''
tests = [
('<html><head><meta property="og:published_time" content="2017-09-01"/></head><body></body></html>',
'2017-09-01', False),
('<html><head><meta property="og:url" content="https://example.org/2017/09/01/content.html"/></head><body></body></html>',
'2017-09-01', False),
('<html><head><meta property="og:url" content="https://example.org/2017/09/01/content.html"/></head><body></body></html>',
'2017-09-01', False),
('<html><body><p>Veröffentlicht am 1.9.17</p></body></html>', '2017-09-01', True),
('<html><body><p>Veröffentlicht am 1.9.17</p></body></html>', '2017-09-01', False),
]
for doc, expected, extensive in tests:
metadata = extract_metadata(doc, extensive=extensive)
assert metadata.date == expected
def test_sitename():
'''Test extraction of site name'''
tests = [
('<html><head><meta name="article:publisher" content="@"/></head><body/></html>', None),
('<html><head><meta name="article:publisher" content="The Newspaper"/></head><body/></html>', 'The Newspaper'),
('<html><head><meta property="article:publisher" content="The Newspaper"/></head><body/></html>', 'The Newspaper'),
('<html><head><title>sitemaps.org - Home</title></head><body/></html>', 'sitemaps.org'),
]
for doc, expected in tests:
metadata = extract_metadata(doc)
assert metadata.sitename == expected
def test_meta():
'''Test extraction out of meta-elements'''
doc = html.fromstring("<html><p class='test'>a</p><p class='other'>b</p><p type='this'>cde</p></html>")
assert extract_metainfo(doc, [XPath(".//p[@class]")]) is None
assert extract_metainfo(doc, [XPath(".//p[@type]")]) == "cde"
metadata = extract_metadata('<html><head><meta property="og:title" content="Open Graph Title"/><meta property="og:author" content="Jenny Smith"/><meta property="og:description" content="This is an Open Graph description"/><meta property="og:site_name" content="My first site"/><meta property="og:url" content="https://example.org/test"/><meta property="og:type" content="Open Graph Type"/></head><body><a rel="license" href="https://creativecommons.org/">Creative Commons</a></body></html>')
assert metadata.pagetype == 'Open Graph Type'
assert metadata.title == 'Open Graph Title'
assert metadata.author == 'Jenny Smith'
assert metadata.description == 'This is an Open Graph description'
assert metadata.sitename == 'My first site'
assert metadata.url == 'https://example.org/test'
assert metadata.license == 'Creative Commons'
metadata = extract_metadata('<html><head><meta name="dc.title" content="Open Graph Title"/><meta name="dc.creator" content="Jenny Smith"/><meta name="dc.description" content="This is an Open Graph description"/></head><body></body></html>')
assert metadata.title == 'Open Graph Title'
assert metadata.author == 'Jenny Smith'
assert metadata.description == 'This is an Open Graph description'
metadata = extract_metadata('<html><head><meta itemprop="headline" content="Title"/></head><body></body></html>')
assert metadata.title == 'Title'
# catch errors
metadata = extract_metadata('')
target_slots = set(metadata.__slots__) - {"body", "commentsbody"}
assert all(getattr(metadata, a) is None for a in target_slots)
metadata = extract_metadata('<html><title></title></html>')
assert metadata.sitename is None
metadata = extract_metadata('<html><head><title>' + 'AAA'*10000 + '</title></head></html>')
assert metadata.title.endswith('') and len(metadata.title) == 10000
assert extract_metadata('<html><head><meta otherkey="example" content="Unknown text"/></head></html>') is not None
assert extract_metadata('<html><head><title></title><title></title><title></title></head></html>') is not None
def test_catstags():
'''Test extraction of categories and tags'''
assert normalize_tags(" ") == ""
assert normalize_tags(" 1 &amp; 2 ") == "1 & 2"
htmldocs = [
'<html><body><p class="entry-categories"><a href="https://example.org/category/cat1/">Cat1</a>, <a href="https://example.org/category/cat2/">Cat2</a></p></body></html>',
'<html><body><div class="postmeta"><a href="https://example.org/category/cat1/">Cat1</a></div></body></html>',
'<html><body><p class="entry-tags"><a href="https://example.org/tags/tag1/">Tag1</a>, <a href="https://example.org/tags/tag2/">Tag2</a></p></body></html>',
'<html><head><meta name="keywords" content="sodium, salt, paracetamol, blood, pressure, high, heart, &amp;quot, intake, warning, study, &amp;quot, medicine, dissolvable, cardiovascular" /></head></html>',
]
attrs = ['categories', 'categories', 'tags', 'tags']
expected = [
['Cat1', 'Cat2'],
['Cat1'],
['Tag1', 'Tag2'],
['sodium, salt, paracetamol, blood, pressure, high, heart, intake, warning, study, medicine, dissolvable, cardiovascular'],
]
for doc, attr, expected_value in zip(htmldocs, attrs, expected):
metadata = extract_metadata(doc)
assert getattr(metadata, attr) == expected_value
def test_license():
'''Test extraction of CC licenses'''
# a rel
metadata = extract_metadata('<html><body><p><a href="https://creativecommons.org/licenses/by-sa/4.0/" rel="license">CC BY-SA</a></p></body></html>')
assert metadata.license == 'CC BY-SA 4.0'
metadata = extract_metadata('<html><body><p><a href="https://licenses.org/unknown" rel="license">Unknown</a></p></body></html>')
assert metadata.license == 'Unknown'
# footer
metadata = extract_metadata('<html><body><footer><a href="https://creativecommons.org/licenses/by-sa/4.0/">CC BY-SA</a></footer></body></html>')
assert metadata.license == 'CC BY-SA 4.0'
# footer: netzpolitik.org
metadata = extract_metadata('''<html><body>
<div class="footer__navigation">
<p class="footer__licence">
<strong>Lizenz: </strong>
Die von uns verfassten Inhalte stehen, soweit nicht anders vermerkt, unter der Lizenz
<a href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Creative Commons BY-NC-SA 4.0.</a>
</p>
</div>
</body></html>''')
assert metadata.license == 'CC BY-NC-SA 4.0'
# this is not a license
metadata = extract_metadata('''<html><body><footer class="entry-footer">
<span class="cat-links">Posted in <a href="https://sallysbakingaddiction.com/category/seasonal/birthday/" rel="category tag">Birthday</a></span>
</footer></body></html>''')
assert metadata.license is None
# this is a license
metadata = extract_metadata('''<html><body><footer class="entry-footer">
<span>The license is <a href="https://example.org/1">CC BY-NC</a></span>
</footer></body></html>''')
assert metadata.license == 'CC BY-NC'
def test_images():
'''Image extraction from meta SEO tags'''
htmldocs = [
'<html><head><meta property="image" content="https://example.org/example.jpg"></html>',
'<html><head><meta property="og:image:url" content="example.jpg"></html>',
'<html><head><meta property="og:image" content="https://example.org/example-opengraph.jpg" /><body/></html>',
'<html><head><meta property="twitter:image" content="https://example.org/example-twitter.jpg"></html>',
'<html><head><meta property="twitter:image:src" content="example-twitter.jpg"></html>',
'<html><head><meta name="robots" content="index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1" /></html>',
]
expected_images = [
'https://example.org/example.jpg',
'example.jpg',
'https://example.org/example-opengraph.jpg',
'https://example.org/example-twitter.jpg',
'example-twitter.jpg',
None,
]
for doc, expected_image in zip(htmldocs, expected_images):
metadata = extract_metadata(doc)
assert metadata.image == expected_image
def test_document_as_dict():
"""Tests that the dict serialization works and preserves data."""
htmldoc = """
<html>
<head>
<title>Test Title</title>
<meta itemprop="author" content="Jenny Smith" />
<meta property="og:url" content="https://example.org" />
<meta itemprop="description" content="Description" />
<meta property="og:published_time" content="2017-09-01" />
<meta name="article:publisher" content="The Newspaper" />
<meta property="image" content="https://example.org/example.jpg" />
</head>
<body>
<p class="entry-categories">
<a href="https://example.org/category/cat1/">Cat1</a>,
<a href="https://example.org/category/cat2/">Cat2</a>
</p>
<p>
<a href="https://creativecommons.org/licenses/by-sa/4.0/" rel="license"
>CC BY-SA</a
>
</p>
</body>
</html>
"""
document = extract_metadata(htmldoc)
dict_ = document.as_dict()
assert dict_["title"] == "Test Title"
assert dict_["author"] == "Jenny Smith"
assert dict_["url"] == "https://example.org"
assert dict_["description"] == "Description"
assert dict_["sitename"] == "The Newspaper"
assert dict_["date"] == "2017-09-01"
assert dict_["categories"] == ["Cat1", "Cat2"]
assert dict_["license"] == "CC BY-SA 4.0"
assert dict_["image"] == "https://example.org/example.jpg"
if __name__ == '__main__':
test_titles()
test_authors()
test_dates()
test_meta()
test_url()
test_description()
test_catstags()
test_sitename()
test_license()
test_images()
test_document_as_dict()