|
@@ -342,6 +342,60 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
|
|
|
'uploader': uploader,
|
|
|
}
|
|
|
|
|
|
+ def _search_results(self, query, params):
|
|
|
+ data = {
|
|
|
+ 'context': {
|
|
|
+ 'client': {
|
|
|
+ 'clientName': 'WEB',
|
|
|
+ 'clientVersion': '2.20201021.03.00',
|
|
|
+ }
|
|
|
+ },
|
|
|
+ 'query': query,
|
|
|
+ }
|
|
|
+ if params:
|
|
|
+ data['params'] = params
|
|
|
+ for page_num in itertools.count(1):
|
|
|
+ search = self._download_json(
|
|
|
+ 'https://www.youtube.com/youtubei/v1/search?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8',
|
|
|
+ video_id='query "%s"' % query,
|
|
|
+ note='Downloading page %s' % page_num,
|
|
|
+ errnote='Unable to download API page', fatal=False,
|
|
|
+ data=json.dumps(data).encode('utf8'),
|
|
|
+ headers={'content-type': 'application/json'})
|
|
|
+ if not search:
|
|
|
+ break
|
|
|
+ slr_contents = try_get(
|
|
|
+ search,
|
|
|
+ (lambda x: x['contents']['twoColumnSearchResultsRenderer']['primaryContents']['sectionListRenderer']['contents'],
|
|
|
+ lambda x: x['onResponseReceivedCommands'][0]['appendContinuationItemsAction']['continuationItems']),
|
|
|
+ list)
|
|
|
+ if not slr_contents:
|
|
|
+ break
|
|
|
+ for slr_content in slr_contents:
|
|
|
+ isr_contents = try_get(
|
|
|
+ slr_content,
|
|
|
+ lambda x: x['itemSectionRenderer']['contents'],
|
|
|
+ list)
|
|
|
+ if not isr_contents:
|
|
|
+ continue
|
|
|
+ for content in isr_contents:
|
|
|
+ if not isinstance(content, dict):
|
|
|
+ continue
|
|
|
+ video = content.get('videoRenderer')
|
|
|
+ if not isinstance(video, dict):
|
|
|
+ continue
|
|
|
+ video_id = video.get('videoId')
|
|
|
+ if not video_id:
|
|
|
+ continue
|
|
|
+ yield self._extract_video(video)
|
|
|
+ token = try_get(
|
|
|
+ slr_contents,
|
|
|
+ lambda x: x[-1]['continuationItemRenderer']['continuationEndpoint']['continuationCommand']['token'],
|
|
|
+ compat_str)
|
|
|
+ if not token:
|
|
|
+ break
|
|
|
+ data['continuation'] = token
|
|
|
+
|
|
|
|
|
|
class YoutubeIE(YoutubeBaseInfoExtractor):
|
|
|
IE_DESC = 'YouTube.com'
|
|
@@ -2135,7 +2189,7 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
|
|
|
(?:
|
|
|
(?:channel|c|user|feed|hashtag)/|
|
|
|
(?:playlist|watch)\?.*?\blist=|
|
|
|
- (?!(?:watch|embed|v|e)\b)
|
|
|
+ (?!(?:watch|embed|v|e|results)\b)
|
|
|
)
|
|
|
(?P<id>[^/?\#&]+)
|
|
|
'''
|
|
@@ -3148,93 +3202,35 @@ class YoutubeFavouritesIE(YoutubeBaseInfoExtractor):
|
|
|
|
|
|
class YoutubeSearchIE(SearchInfoExtractor, YoutubeBaseInfoExtractor):
|
|
|
IE_DESC = 'YouTube.com searches'
|
|
|
- # there doesn't appear to be a real limit, for example if you search for
|
|
|
- # 'python' you get more than 8.000.000 results
|
|
|
- _MAX_RESULTS = float('inf')
|
|
|
IE_NAME = 'youtube:search'
|
|
|
_SEARCH_KEY = 'ytsearch'
|
|
|
- _SEARCH_PARAMS = None
|
|
|
+ _SEARCH_PARAMS = 'EgIQAQ%3D%3D' # Videos only
|
|
|
+ _MAX_RESULTS = float('inf')
|
|
|
_TESTS = []
|
|
|
|
|
|
- def _entries(self, query, n):
|
|
|
- data = {
|
|
|
- 'context': {
|
|
|
- 'client': {
|
|
|
- 'clientName': 'WEB',
|
|
|
- 'clientVersion': '2.20201021.03.00',
|
|
|
- }
|
|
|
- },
|
|
|
- 'query': query,
|
|
|
- }
|
|
|
- if self._SEARCH_PARAMS:
|
|
|
- data['params'] = self._SEARCH_PARAMS
|
|
|
- total = 0
|
|
|
- for page_num in itertools.count(1):
|
|
|
- search = self._download_json(
|
|
|
- 'https://www.youtube.com/youtubei/v1/search?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8',
|
|
|
- video_id='query "%s"' % query,
|
|
|
- note='Downloading page %s' % page_num,
|
|
|
- errnote='Unable to download API page', fatal=False,
|
|
|
- data=json.dumps(data).encode('utf8'),
|
|
|
- headers={'content-type': 'application/json'})
|
|
|
- if not search:
|
|
|
- break
|
|
|
- slr_contents = try_get(
|
|
|
- search,
|
|
|
- (lambda x: x['contents']['twoColumnSearchResultsRenderer']['primaryContents']['sectionListRenderer']['contents'],
|
|
|
- lambda x: x['onResponseReceivedCommands'][0]['appendContinuationItemsAction']['continuationItems']),
|
|
|
- list)
|
|
|
- if not slr_contents:
|
|
|
- break
|
|
|
- for slr_content in slr_contents:
|
|
|
- isr_contents = try_get(
|
|
|
- slr_content,
|
|
|
- lambda x: x['itemSectionRenderer']['contents'],
|
|
|
- list)
|
|
|
- if not isr_contents:
|
|
|
- continue
|
|
|
- for content in isr_contents:
|
|
|
- if not isinstance(content, dict):
|
|
|
- continue
|
|
|
- video = content.get('videoRenderer')
|
|
|
- if not isinstance(video, dict):
|
|
|
- continue
|
|
|
- video_id = video.get('videoId')
|
|
|
- if not video_id:
|
|
|
- continue
|
|
|
- yield self._extract_video(video)
|
|
|
- total += 1
|
|
|
- if total == n:
|
|
|
- return
|
|
|
- token = try_get(
|
|
|
- slr_contents,
|
|
|
- lambda x: x[-1]['continuationItemRenderer']['continuationEndpoint']['continuationCommand']['token'],
|
|
|
- compat_str)
|
|
|
- if not token:
|
|
|
- break
|
|
|
- data['continuation'] = token
|
|
|
-
|
|
|
def _get_n_results(self, query, n):
|
|
|
"""Get a specified number of results for a query"""
|
|
|
- return self.playlist_result(self._entries(query, n), query)
|
|
|
+ entries = itertools.islice(self._search_results(query, self._SEARCH_PARAMS), 0, None if n == float('inf') else n)
|
|
|
+ return self.playlist_result(entries, query, query)
|
|
|
|
|
|
|
|
|
class YoutubeSearchDateIE(YoutubeSearchIE):
|
|
|
IE_NAME = YoutubeSearchIE.IE_NAME + ':date'
|
|
|
_SEARCH_KEY = 'ytsearchdate'
|
|
|
IE_DESC = 'YouTube.com searches, newest videos first'
|
|
|
- _SEARCH_PARAMS = 'CAI%3D'
|
|
|
+ _SEARCH_PARAMS = 'CAISAhAB' # Videos only, sorted by date
|
|
|
+ _TESTS = []
|
|
|
|
|
|
|
|
|
-r"""
|
|
|
-class YoutubeSearchURLIE(YoutubeSearchIE):
|
|
|
- IE_DESC = 'YouTube.com search URLs'
|
|
|
- IE_NAME = 'youtube:search_url'
|
|
|
- _VALID_URL = r'https?://(?:www\.)?youtube\.com/results\?(.*?&)?(?:search_query|q)=(?P<query>[^&]+)(?:[&]|$)'
|
|
|
+class YoutubeSearchURLIE(YoutubeBaseInfoExtractor):
|
|
|
+ IE_DESC = 'YouTube search URLs with sorting and filter support'
|
|
|
+ IE_NAME = YoutubeSearchIE.IE_NAME + '_url'
|
|
|
+ _VALID_URL = r'https?://(?:www\.)?youtube\.com/results\?(.*?&)?(?:search_query|q)=(?:[^&]+)(?:[&]|$)'
|
|
|
_TESTS = [{
|
|
|
'url': 'https://www.youtube.com/results?baz=bar&search_query=youtube-dl+test+video&filters=video&lclk=video',
|
|
|
'playlist_mincount': 5,
|
|
|
'info_dict': {
|
|
|
+ 'id': 'youtube-dl test video',
|
|
|
'title': 'youtube-dl test video',
|
|
|
}
|
|
|
}, {
|
|
@@ -3243,11 +3239,10 @@ class YoutubeSearchURLIE(YoutubeSearchIE):
|
|
|
}]
|
|
|
|
|
|
def _real_extract(self, url):
|
|
|
- mobj = re.match(self._VALID_URL, url)
|
|
|
- query = compat_urllib_parse_unquote_plus(mobj.group('query'))
|
|
|
- webpage = self._download_webpage(url, query)
|
|
|
- return self.playlist_result(self._process_page(webpage), playlist_title=query)
|
|
|
-"""
|
|
|
+ qs = compat_parse_qs(compat_urllib_parse_urlparse(url).query)
|
|
|
+ query = (qs.get('search_query') or qs.get('q'))[0]
|
|
|
+ params = qs.get('sp', ('',))[0]
|
|
|
+ return self.playlist_result(self._search_results(query, params), query, query)
|
|
|
|
|
|
|
|
|
class YoutubeFeedsInfoExtractor(YoutubeTabIE):
|