ytscrape 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ytscrape-0.1.0/.gitignore +32 -0
- ytscrape-0.1.0/LICENSE +21 -0
- ytscrape-0.1.0/PKG-INFO +235 -0
- ytscrape-0.1.0/README.md +183 -0
- ytscrape-0.1.0/pyproject.toml +66 -0
- ytscrape-0.1.0/src/ytscraper/__init__.py +54 -0
- ytscrape-0.1.0/src/ytscraper/__main__.py +92 -0
- ytscrape-0.1.0/src/ytscraper/client.py +169 -0
- ytscrape-0.1.0/src/ytscraper/context.py +60 -0
- ytscrape-0.1.0/src/ytscraper/exceptions.py +19 -0
- ytscrape-0.1.0/src/ytscraper/filters.py +60 -0
- ytscrape-0.1.0/src/ytscraper/locale.py +179 -0
- ytscrape-0.1.0/src/ytscraper/models.py +314 -0
- ytscrape-0.1.0/src/ytscraper/parsing.py +102 -0
- ytscrape-0.1.0/src/ytscraper/py.typed +0 -0
- ytscrape-0.1.0/src/ytscraper/results.py +89 -0
- ytscrape-0.1.0/src/ytscraper/youtube.py +143 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# Distribution / packaging
|
|
7
|
+
build/
|
|
8
|
+
dist/
|
|
9
|
+
*.egg-info/
|
|
10
|
+
.eggs/
|
|
11
|
+
wheels/
|
|
12
|
+
|
|
13
|
+
# Virtual environments
|
|
14
|
+
.venv/
|
|
15
|
+
venv/
|
|
16
|
+
env/
|
|
17
|
+
|
|
18
|
+
# uv
|
|
19
|
+
.uv/
|
|
20
|
+
|
|
21
|
+
# Caches / tooling
|
|
22
|
+
.pytest_cache/
|
|
23
|
+
.mypy_cache/
|
|
24
|
+
.ruff_cache/
|
|
25
|
+
.cache/
|
|
26
|
+
|
|
27
|
+
# IDE
|
|
28
|
+
.idea/
|
|
29
|
+
*.iml
|
|
30
|
+
|
|
31
|
+
# OS
|
|
32
|
+
.DS_Store
|
ytscrape-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 hural
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
ytscrape-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ytscrape
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: YouTube scraper for Python — search videos, channels and playlists and fetch video metadata via the InnerTube API, no API key required.
|
|
5
|
+
Project-URL: Homepage, https://github.com/hural/ytscraper
|
|
6
|
+
Project-URL: Repository, https://github.com/hural/ytscraper
|
|
7
|
+
Project-URL: Issues, https://github.com/hural/ytscraper/issues
|
|
8
|
+
Author: hural
|
|
9
|
+
License: MIT License
|
|
10
|
+
|
|
11
|
+
Copyright (c) 2026 hural
|
|
12
|
+
|
|
13
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
14
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
15
|
+
in the Software without restriction, including without limitation the rights
|
|
16
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
17
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
18
|
+
furnished to do so, subject to the following conditions:
|
|
19
|
+
|
|
20
|
+
The above copyright notice and this permission notice shall be included in all
|
|
21
|
+
copies or substantial portions of the Software.
|
|
22
|
+
|
|
23
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
24
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
25
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
26
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
27
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
28
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
29
|
+
SOFTWARE.
|
|
30
|
+
License-File: LICENSE
|
|
31
|
+
Keywords: crawler,free,innertube,no-api-key,scraper,scraping,search,video,youtube,youtube-api,youtube-data,youtube-scraper
|
|
32
|
+
Classifier: Development Status :: 4 - Beta
|
|
33
|
+
Classifier: Intended Audience :: Developers
|
|
34
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
35
|
+
Classifier: Operating System :: OS Independent
|
|
36
|
+
Classifier: Programming Language :: Python :: 3
|
|
37
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
42
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
43
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
44
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
45
|
+
Classifier: Typing :: Typed
|
|
46
|
+
Requires-Python: >=3.10
|
|
47
|
+
Requires-Dist: build>=1.5.0
|
|
48
|
+
Requires-Dist: pycountry>=22.3.5
|
|
49
|
+
Requires-Dist: requests>=2.28
|
|
50
|
+
Requires-Dist: twine>=7.0.0
|
|
51
|
+
Description-Content-Type: text/markdown
|
|
52
|
+
|
|
53
|
+
# ytscraper — Free YouTube Scraper for Python
|
|
54
|
+
|
|
55
|
+
[](https://pypi.org/project/ytscraper/)
|
|
56
|
+
[](https://pypi.org/project/ytscraper/)
|
|
57
|
+
[](LICENSE)
|
|
58
|
+
|
|
59
|
+
> **Free, open-source YouTube scraper library for Python.** Scrape YouTube
|
|
60
|
+
> search results — videos, channels, playlists and Shorts — plus detailed video
|
|
61
|
+
> metadata, **without an official YouTube Data API key** and **without any quota
|
|
62
|
+
> limits**.
|
|
63
|
+
|
|
64
|
+
`ytscraper` is a **free YouTube scraper and crawler** for Python built on top of
|
|
65
|
+
the internal YouTube *InnerTube* API. Use it to **search YouTube**, **extract
|
|
66
|
+
video, channel and playlist data**, and **fetch video metadata** — all with
|
|
67
|
+
**transparent pagination** and no API key required.
|
|
68
|
+
|
|
69
|
+
Whether you need to **scrape YouTube videos**, **collect channel data**, **mine
|
|
70
|
+
YouTube search results**, or build a **YouTube data extractor** or **dataset**,
|
|
71
|
+
`ytscraper` gives you a simple, Pythonic interface and a clean, extensible
|
|
72
|
+
architecture that is easy to grow with new endpoints and data sources.
|
|
73
|
+
|
|
74
|
+
**Keywords:** free youtube scraper, youtube scraper python, youtube data
|
|
75
|
+
extractor, youtube api without key, scrape youtube videos, youtube channel
|
|
76
|
+
scraper, youtube search scraper, youtube crawler, youtube metadata, python
|
|
77
|
+
youtube scraping library, open source youtube scraper.
|
|
78
|
+
|
|
79
|
+
> ⚠️ This library talks to YouTube's private endpoints. Use it responsibly and
|
|
80
|
+
> at your own risk; the endpoints and `params` values may change over time.
|
|
81
|
+
|
|
82
|
+
## Why ytscraper?
|
|
83
|
+
|
|
84
|
+
- ✅ **Free & open source** (MIT) — no paid plans, no sign-up, no rate-limit
|
|
85
|
+
tiers.
|
|
86
|
+
- 🔑 **No YouTube Data API key required** and **no quota** to worry about.
|
|
87
|
+
- 🐍 **Pure Python** with full type hints and a tiny, dependency-light install.
|
|
88
|
+
- ⚡ Simple, Pythonic API — start scraping YouTube in just a few lines.
|
|
89
|
+
|
|
90
|
+
## Features
|
|
91
|
+
|
|
92
|
+
- 🔎 Search videos, channels and playlists with a clean `SearchFilter` enum
|
|
93
|
+
(no magic `EgIQAQ==` strings in your code).
|
|
94
|
+
- 📄 **Transparent pagination** — just iterate; continuation tokens are handled
|
|
95
|
+
for you.
|
|
96
|
+
- 🎬 Fetch detailed video metadata from an id or any YouTube URL.
|
|
97
|
+
- 🌍 **Language & country support** — localise results with the `Language`
|
|
98
|
+
and `Country` value objects (thin wrappers around raw ISO codes, no
|
|
99
|
+
hard-coded lists) bundled in a small `Locale` object; codes are validated
|
|
100
|
+
with `pycountry`, so typos fail fast.
|
|
101
|
+
- 🧱 Extensible OOP design (facade + factory methods + strategy) and full type
|
|
102
|
+
hints — easy to build on.
|
|
103
|
+
- 🖥️ A tiny CLI: `python -m ytscraper ...`.
|
|
104
|
+
|
|
105
|
+
## Installation
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
pip install ytscraper
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
From source:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
pip install .
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Quick start
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
from ytscraper import YouTube, SearchFilter
|
|
121
|
+
|
|
122
|
+
with YouTube() as yt:
|
|
123
|
+
# Search videos and iterate over as many pages as needed.
|
|
124
|
+
for video in yt.search("python", filter=SearchFilter.VIDEOS, max_results=20):
|
|
125
|
+
print(video.title, "-", video.url)
|
|
126
|
+
|
|
127
|
+
# Search channels.
|
|
128
|
+
for channel in yt.search("python", filter=SearchFilter.CHANNELS, max_results=5):
|
|
129
|
+
print(channel.title, channel.url)
|
|
130
|
+
|
|
131
|
+
# Fetch details for a single video (id or URL both work).
|
|
132
|
+
details = yt.video("https://www.youtube.com/watch?v=dQw4w9WgXcQ")
|
|
133
|
+
print(details.title, details.channel, details.views)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Search filters
|
|
137
|
+
|
|
138
|
+
| Filter | Description |
|
|
139
|
+
| -------------------------- | ------------------- |
|
|
140
|
+
| `SearchFilter.ALL` | Everything (default)|
|
|
141
|
+
| `SearchFilter.VIDEOS` | Videos only |
|
|
142
|
+
| `SearchFilter.CHANNELS` | Channels only |
|
|
143
|
+
| `SearchFilter.PLAYLISTS` | Playlists only |
|
|
144
|
+
| `SearchFilter.SHORTS` | Shorts only |
|
|
145
|
+
| `SearchFilter.MOVIES` | Movies only |
|
|
146
|
+
|
|
147
|
+
You can also pass the string form: `yt.search("python", filter="videos")`.
|
|
148
|
+
|
|
149
|
+
### Language & country
|
|
150
|
+
|
|
151
|
+
YouTube localises results by *interface language* (`hl`) and *content region*
|
|
152
|
+
(`gl`). Configure both when creating `YouTube`, passing plain ISO codes (or the
|
|
153
|
+
`Language` / `Country` value objects, which wrap the same codes):
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
from ytscraper import YouTube, Language, Country, Locale
|
|
157
|
+
|
|
158
|
+
# Just pass raw ISO codes — they are validated and normalised for you.
|
|
159
|
+
with YouTube(language="uk", region="UA") as yt:
|
|
160
|
+
for video in yt.search("музика", max_results=10):
|
|
161
|
+
print(video.title, video.url)
|
|
162
|
+
|
|
163
|
+
# The Language / Country value objects are equivalent (and reusable).
|
|
164
|
+
yt = YouTube(language=Language("de"), region=Country("DE"))
|
|
165
|
+
|
|
166
|
+
# Invalid codes are rejected early (validated with pycountry).
|
|
167
|
+
YouTube(region="XX") # ValueError: Unknown country code 'XX'. ...
|
|
168
|
+
|
|
169
|
+
# Or pass a ready-made Locale.
|
|
170
|
+
yt = YouTube(locale=Locale(language="fr", country="FR"))
|
|
171
|
+
print(yt.locale.language.code, yt.locale.country.code) # fr FR
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
`Language` and `Country` are thin, self-validating value objects around a raw
|
|
175
|
+
ISO 639-1 / ISO 3166-1 alpha-2 code — there is no hard-coded list of languages
|
|
176
|
+
or countries, so any valid code works. Codes are validated with
|
|
177
|
+
[`pycountry`](https://pypi.org/project/pycountry/): an unknown language or
|
|
178
|
+
country code raises a `ValueError` instead of silently producing a broken
|
|
179
|
+
request. The chosen locale is sent both in the request context (`hl` / `gl`)
|
|
180
|
+
and as the `Accept-Language` HTTP header.
|
|
181
|
+
|
|
182
|
+
### Pagination
|
|
183
|
+
|
|
184
|
+
Pagination is transparent — iterating over the result object automatically
|
|
185
|
+
loads the next page:
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
results = yt.search("python")
|
|
189
|
+
|
|
190
|
+
for item in results: # loads pages on demand
|
|
191
|
+
print(item.title)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
You can also page manually:
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
results = yt.search("python")
|
|
198
|
+
print(len(results.fetch_next_page())) # explicitly load one more page
|
|
199
|
+
print(results.has_more) # is there another page?
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Use `max_results` to cap how many items you consume.
|
|
203
|
+
|
|
204
|
+
## Use cases
|
|
205
|
+
|
|
206
|
+
`ytscraper` is a great fit when you want to:
|
|
207
|
+
|
|
208
|
+
- **Scrape YouTube search results** for a keyword or topic.
|
|
209
|
+
- **Extract YouTube video data** (title, channel, views, duration, thumbnails).
|
|
210
|
+
- **Collect YouTube channel and playlist listings** at scale.
|
|
211
|
+
- **Build a YouTube dataset** for research, analytics or machine learning.
|
|
212
|
+
- **Monitor YouTube** content for a niche without hitting API quotas.
|
|
213
|
+
|
|
214
|
+
## Command line
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
# Search
|
|
218
|
+
python -m ytscraper search "python tutorial" --filter videos --max 10
|
|
219
|
+
|
|
220
|
+
# Localised search (Ukrainian interface, Ukrainian region)
|
|
221
|
+
python -m ytscraper --language uk --region UA search "музика" --max 10
|
|
222
|
+
|
|
223
|
+
# Video details
|
|
224
|
+
python -m ytscraper video https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
After installing, a `ytscraper` console script is also available:
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
ytscraper search "python" --filter channels --max 5
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## License
|
|
234
|
+
|
|
235
|
+
[MIT](LICENSE)
|
ytscrape-0.1.0/README.md
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# ytscraper — Free YouTube Scraper for Python
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/ytscraper/)
|
|
4
|
+
[](https://pypi.org/project/ytscraper/)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
> **Free, open-source YouTube scraper library for Python.** Scrape YouTube
|
|
8
|
+
> search results — videos, channels, playlists and Shorts — plus detailed video
|
|
9
|
+
> metadata, **without an official YouTube Data API key** and **without any quota
|
|
10
|
+
> limits**.
|
|
11
|
+
|
|
12
|
+
`ytscraper` is a **free YouTube scraper and crawler** for Python built on top of
|
|
13
|
+
the internal YouTube *InnerTube* API. Use it to **search YouTube**, **extract
|
|
14
|
+
video, channel and playlist data**, and **fetch video metadata** — all with
|
|
15
|
+
**transparent pagination** and no API key required.
|
|
16
|
+
|
|
17
|
+
Whether you need to **scrape YouTube videos**, **collect channel data**, **mine
|
|
18
|
+
YouTube search results**, or build a **YouTube data extractor** or **dataset**,
|
|
19
|
+
`ytscraper` gives you a simple, Pythonic interface and a clean, extensible
|
|
20
|
+
architecture that is easy to grow with new endpoints and data sources.
|
|
21
|
+
|
|
22
|
+
**Keywords:** free youtube scraper, youtube scraper python, youtube data
|
|
23
|
+
extractor, youtube api without key, scrape youtube videos, youtube channel
|
|
24
|
+
scraper, youtube search scraper, youtube crawler, youtube metadata, python
|
|
25
|
+
youtube scraping library, open source youtube scraper.
|
|
26
|
+
|
|
27
|
+
> ⚠️ This library talks to YouTube's private endpoints. Use it responsibly and
|
|
28
|
+
> at your own risk; the endpoints and `params` values may change over time.
|
|
29
|
+
|
|
30
|
+
## Why ytscraper?
|
|
31
|
+
|
|
32
|
+
- ✅ **Free & open source** (MIT) — no paid plans, no sign-up, no rate-limit
|
|
33
|
+
tiers.
|
|
34
|
+
- 🔑 **No YouTube Data API key required** and **no quota** to worry about.
|
|
35
|
+
- 🐍 **Pure Python** with full type hints and a tiny, dependency-light install.
|
|
36
|
+
- ⚡ Simple, Pythonic API — start scraping YouTube in just a few lines.
|
|
37
|
+
|
|
38
|
+
## Features
|
|
39
|
+
|
|
40
|
+
- 🔎 Search videos, channels and playlists with a clean `SearchFilter` enum
|
|
41
|
+
(no magic `EgIQAQ==` strings in your code).
|
|
42
|
+
- 📄 **Transparent pagination** — just iterate; continuation tokens are handled
|
|
43
|
+
for you.
|
|
44
|
+
- 🎬 Fetch detailed video metadata from an id or any YouTube URL.
|
|
45
|
+
- 🌍 **Language & country support** — localise results with the `Language`
|
|
46
|
+
and `Country` value objects (thin wrappers around raw ISO codes, no
|
|
47
|
+
hard-coded lists) bundled in a small `Locale` object; codes are validated
|
|
48
|
+
with `pycountry`, so typos fail fast.
|
|
49
|
+
- 🧱 Extensible OOP design (facade + factory methods + strategy) and full type
|
|
50
|
+
hints — easy to build on.
|
|
51
|
+
- 🖥️ A tiny CLI: `python -m ytscraper ...`.
|
|
52
|
+
|
|
53
|
+
## Installation
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install ytscraper
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
From source:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install .
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Quick start
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from ytscraper import YouTube, SearchFilter
|
|
69
|
+
|
|
70
|
+
with YouTube() as yt:
|
|
71
|
+
# Search videos and iterate over as many pages as needed.
|
|
72
|
+
for video in yt.search("python", filter=SearchFilter.VIDEOS, max_results=20):
|
|
73
|
+
print(video.title, "-", video.url)
|
|
74
|
+
|
|
75
|
+
# Search channels.
|
|
76
|
+
for channel in yt.search("python", filter=SearchFilter.CHANNELS, max_results=5):
|
|
77
|
+
print(channel.title, channel.url)
|
|
78
|
+
|
|
79
|
+
# Fetch details for a single video (id or URL both work).
|
|
80
|
+
details = yt.video("https://www.youtube.com/watch?v=dQw4w9WgXcQ")
|
|
81
|
+
print(details.title, details.channel, details.views)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Search filters
|
|
85
|
+
|
|
86
|
+
| Filter | Description |
|
|
87
|
+
| -------------------------- | ------------------- |
|
|
88
|
+
| `SearchFilter.ALL` | Everything (default)|
|
|
89
|
+
| `SearchFilter.VIDEOS` | Videos only |
|
|
90
|
+
| `SearchFilter.CHANNELS` | Channels only |
|
|
91
|
+
| `SearchFilter.PLAYLISTS` | Playlists only |
|
|
92
|
+
| `SearchFilter.SHORTS` | Shorts only |
|
|
93
|
+
| `SearchFilter.MOVIES` | Movies only |
|
|
94
|
+
|
|
95
|
+
You can also pass the string form: `yt.search("python", filter="videos")`.
|
|
96
|
+
|
|
97
|
+
### Language & country
|
|
98
|
+
|
|
99
|
+
YouTube localises results by *interface language* (`hl`) and *content region*
|
|
100
|
+
(`gl`). Configure both when creating `YouTube`, passing plain ISO codes (or the
|
|
101
|
+
`Language` / `Country` value objects, which wrap the same codes):
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
from ytscraper import YouTube, Language, Country, Locale
|
|
105
|
+
|
|
106
|
+
# Just pass raw ISO codes — they are validated and normalised for you.
|
|
107
|
+
with YouTube(language="uk", region="UA") as yt:
|
|
108
|
+
for video in yt.search("музика", max_results=10):
|
|
109
|
+
print(video.title, video.url)
|
|
110
|
+
|
|
111
|
+
# The Language / Country value objects are equivalent (and reusable).
|
|
112
|
+
yt = YouTube(language=Language("de"), region=Country("DE"))
|
|
113
|
+
|
|
114
|
+
# Invalid codes are rejected early (validated with pycountry).
|
|
115
|
+
YouTube(region="XX") # ValueError: Unknown country code 'XX'. ...
|
|
116
|
+
|
|
117
|
+
# Or pass a ready-made Locale.
|
|
118
|
+
yt = YouTube(locale=Locale(language="fr", country="FR"))
|
|
119
|
+
print(yt.locale.language.code, yt.locale.country.code) # fr FR
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`Language` and `Country` are thin, self-validating value objects around a raw
|
|
123
|
+
ISO 639-1 / ISO 3166-1 alpha-2 code — there is no hard-coded list of languages
|
|
124
|
+
or countries, so any valid code works. Codes are validated with
|
|
125
|
+
[`pycountry`](https://pypi.org/project/pycountry/): an unknown language or
|
|
126
|
+
country code raises a `ValueError` instead of silently producing a broken
|
|
127
|
+
request. The chosen locale is sent both in the request context (`hl` / `gl`)
|
|
128
|
+
and as the `Accept-Language` HTTP header.
|
|
129
|
+
|
|
130
|
+
### Pagination
|
|
131
|
+
|
|
132
|
+
Pagination is transparent — iterating over the result object automatically
|
|
133
|
+
loads the next page:
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
results = yt.search("python")
|
|
137
|
+
|
|
138
|
+
for item in results: # loads pages on demand
|
|
139
|
+
print(item.title)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
You can also page manually:
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
results = yt.search("python")
|
|
146
|
+
print(len(results.fetch_next_page())) # explicitly load one more page
|
|
147
|
+
print(results.has_more) # is there another page?
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Use `max_results` to cap how many items you consume.
|
|
151
|
+
|
|
152
|
+
## Use cases
|
|
153
|
+
|
|
154
|
+
`ytscraper` is a great fit when you want to:
|
|
155
|
+
|
|
156
|
+
- **Scrape YouTube search results** for a keyword or topic.
|
|
157
|
+
- **Extract YouTube video data** (title, channel, views, duration, thumbnails).
|
|
158
|
+
- **Collect YouTube channel and playlist listings** at scale.
|
|
159
|
+
- **Build a YouTube dataset** for research, analytics or machine learning.
|
|
160
|
+
- **Monitor YouTube** content for a niche without hitting API quotas.
|
|
161
|
+
|
|
162
|
+
## Command line
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
# Search
|
|
166
|
+
python -m ytscraper search "python tutorial" --filter videos --max 10
|
|
167
|
+
|
|
168
|
+
# Localised search (Ukrainian interface, Ukrainian region)
|
|
169
|
+
python -m ytscraper --language uk --region UA search "музика" --max 10
|
|
170
|
+
|
|
171
|
+
# Video details
|
|
172
|
+
python -m ytscraper video https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
After installing, a `ytscraper` console script is also available:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
ytscraper search "python" --filter channels --max 5
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## License
|
|
182
|
+
|
|
183
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ytscrape"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "YouTube scraper for Python — search videos, channels and playlists and fetch video metadata via the InnerTube API, no API key required."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { file = "LICENSE" }
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
keywords = [
|
|
13
|
+
"youtube",
|
|
14
|
+
"youtube-scraper",
|
|
15
|
+
"scraper",
|
|
16
|
+
"crawler",
|
|
17
|
+
"innertube",
|
|
18
|
+
"youtube-api",
|
|
19
|
+
"youtube-data",
|
|
20
|
+
"video",
|
|
21
|
+
"scraping",
|
|
22
|
+
"search",
|
|
23
|
+
"free",
|
|
24
|
+
"no-api-key",
|
|
25
|
+
]
|
|
26
|
+
authors = [{ name = "hural" }]
|
|
27
|
+
classifiers = [
|
|
28
|
+
"Development Status :: 4 - Beta",
|
|
29
|
+
"Intended Audience :: Developers",
|
|
30
|
+
"License :: OSI Approved :: MIT License",
|
|
31
|
+
"Operating System :: OS Independent",
|
|
32
|
+
"Programming Language :: Python :: 3",
|
|
33
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
34
|
+
"Programming Language :: Python :: 3.10",
|
|
35
|
+
"Programming Language :: Python :: 3.11",
|
|
36
|
+
"Programming Language :: Python :: 3.12",
|
|
37
|
+
"Programming Language :: Python :: 3.13",
|
|
38
|
+
"Programming Language :: Python :: 3.14",
|
|
39
|
+
"Topic :: Internet :: WWW/HTTP",
|
|
40
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
41
|
+
"Typing :: Typed",
|
|
42
|
+
]
|
|
43
|
+
dependencies = [
|
|
44
|
+
"requests>=2.28",
|
|
45
|
+
"pycountry>=22.3.5",
|
|
46
|
+
"build>=1.5.0",
|
|
47
|
+
"twine>=7.0.0",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
[project.urls]
|
|
51
|
+
Homepage = "https://github.com/hural/ytscraper"
|
|
52
|
+
Repository = "https://github.com/hural/ytscraper"
|
|
53
|
+
Issues = "https://github.com/hural/ytscraper/issues"
|
|
54
|
+
|
|
55
|
+
[project.scripts]
|
|
56
|
+
ytscraper = "ytscraper.__main__:main"
|
|
57
|
+
|
|
58
|
+
[tool.hatch.build.targets.wheel]
|
|
59
|
+
packages = ["src/ytscraper"]
|
|
60
|
+
|
|
61
|
+
[tool.hatch.build.targets.sdist]
|
|
62
|
+
include = [
|
|
63
|
+
"src/ytscraper",
|
|
64
|
+
"README.md",
|
|
65
|
+
"LICENSE",
|
|
66
|
+
]
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""ytscraper -- a small, object-oriented YouTube scraper.
|
|
2
|
+
|
|
3
|
+
The package exposes a single high level facade, :class:`YouTube`, together with
|
|
4
|
+
the data models and the :class:`SearchFilter` enum::
|
|
5
|
+
|
|
6
|
+
from ytscraper import YouTube, SearchFilter
|
|
7
|
+
|
|
8
|
+
with YouTube() as yt:
|
|
9
|
+
for video in yt.search("python", filter=SearchFilter.VIDEOS,
|
|
10
|
+
max_results=20):
|
|
11
|
+
print(video.title, video.url)
|
|
12
|
+
|
|
13
|
+
details = yt.video("dQw4w9WgXcQ")
|
|
14
|
+
print(details.title, details.views)
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from .client import InnerTubeClient
|
|
20
|
+
from .context import ContextExtractor, InnerTubeContext
|
|
21
|
+
from .exceptions import (
|
|
22
|
+
ContextExtractionError,
|
|
23
|
+
ParseError,
|
|
24
|
+
RequestError,
|
|
25
|
+
YtScraperError,
|
|
26
|
+
)
|
|
27
|
+
from .filters import SearchFilter
|
|
28
|
+
from .locale import Country, Language, Locale
|
|
29
|
+
from .models import Channel, Playlist, Video, VideoDetails
|
|
30
|
+
from .results import SearchResults
|
|
31
|
+
from .youtube import YouTube
|
|
32
|
+
|
|
33
|
+
__version__ = "0.1.0"
|
|
34
|
+
|
|
35
|
+
__all__ = [
|
|
36
|
+
"YouTube",
|
|
37
|
+
"SearchFilter",
|
|
38
|
+
"Language",
|
|
39
|
+
"Country",
|
|
40
|
+
"Locale",
|
|
41
|
+
"SearchResults",
|
|
42
|
+
"Video",
|
|
43
|
+
"Channel",
|
|
44
|
+
"Playlist",
|
|
45
|
+
"VideoDetails",
|
|
46
|
+
"InnerTubeClient",
|
|
47
|
+
"InnerTubeContext",
|
|
48
|
+
"ContextExtractor",
|
|
49
|
+
"YtScraperError",
|
|
50
|
+
"ContextExtractionError",
|
|
51
|
+
"RequestError",
|
|
52
|
+
"ParseError",
|
|
53
|
+
"__version__",
|
|
54
|
+
]
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""Minimal command line interface: ``python -m ytscraper``.
|
|
2
|
+
|
|
3
|
+
Examples:
|
|
4
|
+
python -m ytscraper search "python tutorial" --filter videos --max 10
|
|
5
|
+
python -m ytscraper video https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
from . import YouTube, __version__
|
|
14
|
+
from .exceptions import YtScraperError
|
|
15
|
+
from .filters import SearchFilter
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
19
|
+
parser = argparse.ArgumentParser(
|
|
20
|
+
prog="ytscraper",
|
|
21
|
+
description="Scrape YouTube search results and video metadata.",
|
|
22
|
+
)
|
|
23
|
+
parser.add_argument(
|
|
24
|
+
"--version", action="version", version=f"ytscraper {__version__}"
|
|
25
|
+
)
|
|
26
|
+
parser.add_argument(
|
|
27
|
+
"--language",
|
|
28
|
+
default="en",
|
|
29
|
+
metavar="HL",
|
|
30
|
+
help="Interface language (hl), e.g. en, uk, de.",
|
|
31
|
+
)
|
|
32
|
+
parser.add_argument(
|
|
33
|
+
"--region",
|
|
34
|
+
default="US",
|
|
35
|
+
metavar="GL",
|
|
36
|
+
help="Content region / country (gl), e.g. US, UA, DE.",
|
|
37
|
+
)
|
|
38
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
39
|
+
|
|
40
|
+
search = sub.add_parser("search", help="Search YouTube.")
|
|
41
|
+
search.add_argument("query", help="Search query.")
|
|
42
|
+
search.add_argument(
|
|
43
|
+
"--filter",
|
|
44
|
+
choices=[f.value for f in SearchFilter],
|
|
45
|
+
default=SearchFilter.ALL.value,
|
|
46
|
+
help="Type of results to return.",
|
|
47
|
+
)
|
|
48
|
+
search.add_argument(
|
|
49
|
+
"--max",
|
|
50
|
+
type=int,
|
|
51
|
+
default=10,
|
|
52
|
+
dest="max_results",
|
|
53
|
+
help="Maximum number of results to print.",
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
video = sub.add_parser("video", help="Fetch details for a single video.")
|
|
57
|
+
video.add_argument("video", help="Video id or URL.")
|
|
58
|
+
|
|
59
|
+
return parser
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def main(argv: list[str] | None = None) -> int:
|
|
63
|
+
"""Entry point for the console script and ``python -m ytscraper``."""
|
|
64
|
+
args = _build_parser().parse_args(argv)
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
with YouTube(language=args.language, region=args.region) as yt:
|
|
68
|
+
if args.command == "search":
|
|
69
|
+
results = yt.search(
|
|
70
|
+
args.query,
|
|
71
|
+
filter=args.filter,
|
|
72
|
+
max_results=args.max_results,
|
|
73
|
+
)
|
|
74
|
+
for item in results:
|
|
75
|
+
title = getattr(item, "title", None)
|
|
76
|
+
print(f"{title}\t{item.url}")
|
|
77
|
+
elif args.command == "video":
|
|
78
|
+
details = yt.video(args.video)
|
|
79
|
+
print(f"Title: {details.title}")
|
|
80
|
+
print(f"Channel: {details.channel}")
|
|
81
|
+
print(f"Views: {details.views}")
|
|
82
|
+
print(f"Length: {details.length_seconds}s")
|
|
83
|
+
print(f"URL: {details.url}")
|
|
84
|
+
except YtScraperError as exc:
|
|
85
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
86
|
+
return 1
|
|
87
|
+
|
|
88
|
+
return 0
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
if __name__ == "__main__":
|
|
92
|
+
raise SystemExit(main())
|