1
0
mirror of https://github.com/ytdl-org/youtube-dl.git synced 2024-12-23 00:16:48 +00:00

Compare commits

...

4 Commits

Author SHA1 Message Date
Zenon Mousmoulas
832343a95a
Merge a2fd63ce22 into c5098961b0 2024-08-07 08:48:30 +03:00
Zenon Mousmoulas
a2fd63ce22 JSON-LD: Tweak (News)Article description extraction
Let JSON-LD extract description from articleBody and fall back to
description field when processing (News)Article typed nodes
2021-11-12 09:35:50 +02:00
Zenon Mousmoulas
d6469de1da Extend TestInfoExtractor.test_search_json_ld_realworld to cover @graph
expressing JSON-LD implicit default graph

* Refactor tests in a list of 3-tuples: test html string, expected dict,
  keyword args for InfoExtractor._search_json_ld
* Adapt test code accordingly
* Add test for @graph expressing JSON-LD implicit default graph
2021-11-12 09:35:50 +02:00
Zenon Mousmoulas
77e8f5353c JSON-LD: Support top-level @graph expressing implicit default graph
Per W3C JSON-LD v1.1 §4.9 (non-normative ref):

    When a JSON-LD document's top-level structure is a map that contains
    no other keys than @graph and optionally @context (properties that
    are not mapped to an IRI or a keyword are ignored), @graph is
    considered to express the otherwise implicit default graph.

Support such a structure in InfoExtractor._json_ld parsing:
Wrap the control flow block in a function, which is called recursively
upon such a structure
2021-11-12 09:30:17 +02:00
2 changed files with 97 additions and 18 deletions

View File

@ -187,10 +187,10 @@ class TestInfoExtractor(unittest.TestCase):
self.assertEqual(search['track']['id'], 'testid') self.assertEqual(search['track']['id'], 'testid')
def test_search_json_ld_realworld(self): def test_search_json_ld_realworld(self):
# https://github.com/ytdl-org/youtube-dl/issues/23306 _TESTS = [
expect_dict( # https://github.com/ytdl-org/youtube-dl/issues/23306
self, (
self.ie._search_json_ld(r'''<script type="application/ld+json"> r'''<script type="application/ld+json">
{ {
"@context": "http://schema.org/", "@context": "http://schema.org/",
"@type": "VideoObject", "@type": "VideoObject",
@ -223,17 +223,86 @@ class TestInfoExtractor(unittest.TestCase):
"name": "Kleio Valentien", "name": "Kleio Valentien",
"url": "https://www.eporner.com/pornstar/kleio-valentien/" "url": "https://www.eporner.com/pornstar/kleio-valentien/"
}]} }]}
</script>''', None), </script>''',
{ {
'title': '1 On 1 With Kleio', 'title': '1 On 1 With Kleio',
'description': 'Kleio Valentien', 'description': 'Kleio Valentien',
'url': 'https://gvideo.eporner.com/xN49A1cT3eB/xN49A1cT3eB.mp4', 'url': 'https://gvideo.eporner.com/xN49A1cT3eB/xN49A1cT3eB.mp4',
'timestamp': 1449347075, 'timestamp': 1449347075,
'duration': 743.0, 'duration': 743.0,
'view_count': 1120958, 'view_count': 1120958,
'width': 1920, 'width': 1920,
'height': 1080, 'height': 1080,
}) },
{},
),
(
r'''<script type="application/ld+json">
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "NewsArticle",
"mainEntityOfPage": {
"@type": "WebPage",
"@id": "https://www.ant1news.gr/Society/article/620286/symmoria-anilikon-dikigoros-thymaton-ithelan-na-toys-apoteleiosoyn"
},
"headline": "Συμμορία ανηλίκων δικηγόρος θυμάτων: ήθελαν να τους αποτελειώσουν",
"name": "Συμμορία ανηλίκων δικηγόρος θυμάτων: ήθελαν να τους αποτελειώσουν",
"description": "Τα παιδιά δέχθηκαν την επίθεση επειδή αρνήθηκαν να γίνουν μέλη της συμμορίας, ανέφερε ο Γ. Ζαχαρόπουλος.",
"image": {
"@type": "ImageObject",
"url": "https://ant1media.azureedge.net/imgHandler/1100/a635c968-be71-447c-bf9c-80d843ece21e.jpg",
"width": 1100,
"height": 756 },
"datePublished": "2021-11-10T08:50:00+03:00",
"dateModified": "2021-11-10T08:52:53+03:00",
"author": {
"@type": "Person",
"@id": "https://www.ant1news.gr/",
"name": "Ant1news",
"image": "https://www.ant1news.gr/images/logo-e5d7e4b3e714c88e8d2eca96130142f6.png",
"url": "https://www.ant1news.gr/"
},
"publisher": {
"@type": "Organization",
"@id": "https://www.ant1news.gr#publisher",
"name": "Ant1news",
"url": "https://www.ant1news.gr",
"logo": {
"@type": "ImageObject",
"url": "https://www.ant1news.gr/images/logo-e5d7e4b3e714c88e8d2eca96130142f6.png",
"width": 400,
"height": 400 },
"sameAs": [
"https://www.facebook.com/Ant1news.gr",
"https://twitter.com/antennanews",
"https://www.youtube.com/channel/UC0smvAbfczoN75dP0Hw4Pzw",
"https://www.instagram.com/ant1news/"
]
},
"keywords": "μαχαίρωμα,συμμορία ανηλίκων,ΕΙΔΗΣΕΙΣ,ΕΙΔΗΣΕΙΣ ΣΗΜΕΡΑ,ΝΕΑ,Κοινωνία - Ant1news",
"articleSection": "Κοινωνία"
}
]
}
</script>''',
{
'timestamp': 1636523400,
'title': 'md5:91fe569e952e4d146485740ae927662b',
},
{'expected_type': 'NewsArticle'},
),
]
for html, expected_dict, search_json_ld_kwargs in _TESTS:
expect_dict(
self,
self.ie._search_json_ld(html, None, **search_json_ld_kwargs),
expected_dict
)
def test_download_json(self): def test_download_json(self):
uri = encode_data_uri(b'{"foo": "blah"}', 'application/json') uri = encode_data_uri(b'{"foo": "blah"}', 'application/json')

View File

@ -1438,8 +1438,16 @@ class InfoExtractor(object):
}) })
extract_interaction_statistic(e) extract_interaction_statistic(e)
for e in json_ld: def traverse_json_ld(json_ld, info, at_top_level=True):
if '@context' in e: for e in json_ld:
if at_top_level and '@context' not in e:
continue
if at_top_level and all(k in ('@context', '@graph') for k in e):
graph = e['@graph']
if isinstance(graph, dict):
graph = [graph]
traverse_json_ld(graph, info, at_top_level=False)
break
item_type = e.get('@type') item_type = e.get('@type')
if expected_type is not None and expected_type != item_type: if expected_type is not None and expected_type != item_type:
continue continue
@ -1472,7 +1480,7 @@ class InfoExtractor(object):
info.update({ info.update({
'timestamp': parse_iso8601(e.get('datePublished')), 'timestamp': parse_iso8601(e.get('datePublished')),
'title': unescapeHTML(e.get('headline')), 'title': unescapeHTML(e.get('headline')),
'description': unescapeHTML(e.get('articleBody')), 'description': unescapeHTML(e.get('articleBody') or e.get('description')),
}) })
elif item_type == 'VideoObject': elif item_type == 'VideoObject':
extract_video_object(e) extract_video_object(e)
@ -1487,6 +1495,8 @@ class InfoExtractor(object):
continue continue
else: else:
break break
traverse_json_ld(json_ld, info)
return dict((k, v) for k, v in info.items() if v is not None) return dict((k, v) for k, v in info.items() if v is not None)
def _search_nextjs_data(self, webpage, video_id, **kw): def _search_nextjs_data(self, webpage, video_id, **kw):