py-youtube-search 0.2.2__tar.gz → 0.2.4__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.
- {py_youtube_search-0.2.2 → py_youtube_search-0.2.4}/PKG-INFO +107 -10
- py_youtube_search-0.2.4/README.md +210 -0
- py_youtube_search-0.2.4/py_youtube_search/__init__.py +176 -0
- {py_youtube_search-0.2.2 → py_youtube_search-0.2.4}/py_youtube_search.egg-info/PKG-INFO +107 -10
- py_youtube_search-0.2.4/py_youtube_search.egg-info/requires.txt +2 -0
- {py_youtube_search-0.2.2 → py_youtube_search-0.2.4}/setup.py +2 -1
- py_youtube_search-0.2.2/README.md +0 -114
- py_youtube_search-0.2.2/py_youtube_search/__init__.py +0 -96
- py_youtube_search-0.2.2/py_youtube_search.egg-info/requires.txt +0 -1
- {py_youtube_search-0.2.2 → py_youtube_search-0.2.4}/py_youtube_search.egg-info/SOURCES.txt +0 -0
- {py_youtube_search-0.2.2 → py_youtube_search-0.2.4}/py_youtube_search.egg-info/dependency_links.txt +0 -0
- {py_youtube_search-0.2.2 → py_youtube_search-0.2.4}/py_youtube_search.egg-info/top_level.txt +0 -0
- {py_youtube_search-0.2.2 → py_youtube_search-0.2.4}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: py-youtube-search
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: A lightweight, regex-based YouTube search library without API keys.
|
|
5
5
|
Home-page: https://github.com/VishvaRam/py-youtube-search
|
|
6
6
|
Author: VishvaRam
|
|
@@ -12,6 +12,7 @@ Classifier: Operating System :: OS Independent
|
|
|
12
12
|
Requires-Python: >=3.6
|
|
13
13
|
Description-Content-Type: text/markdown
|
|
14
14
|
Requires-Dist: aiohttp>=3.8.0
|
|
15
|
+
Requires-Dist: requests>=2.25.0
|
|
15
16
|
Dynamic: author
|
|
16
17
|
Dynamic: author-email
|
|
17
18
|
Dynamic: classifier
|
|
@@ -25,12 +26,12 @@ Dynamic: summary
|
|
|
25
26
|
|
|
26
27
|
# py-youtube-search
|
|
27
28
|
|
|
28
|
-
A lightweight
|
|
29
|
-
It scrapes search results using
|
|
29
|
+
A lightweight Python library to search YouTube videos programmatically without an API key.
|
|
30
|
+
It scrapes search results using regex, making it fast, robust, and perfect for both synchronous and asynchronous applications.
|
|
30
31
|
|
|
31
32
|
## Features
|
|
32
33
|
|
|
33
|
-
- **Async Support**:
|
|
34
|
+
- **Async & Sync Support**: Choose between fully asynchronous (`aiohttp`) or synchronous (`requests`) implementations.
|
|
34
35
|
- **Reusable Client**: Create a single instance and run multiple searches with different configurations.
|
|
35
36
|
- **No API Key Required**: Search YouTube directly without setting up Google Cloud projects.
|
|
36
37
|
- **Advanced Filtering**: Built-in support for duration (Medium 3-20m, Long >20m) and upload date filters.
|
|
@@ -44,15 +45,15 @@ pip install py-youtube-search
|
|
|
44
45
|
|
|
45
46
|
## Quick Start
|
|
46
47
|
|
|
47
|
-
### 1.
|
|
48
|
-
|
|
48
|
+
### 1. Async Search (Recommended for Concurrent Operations)
|
|
49
|
+
Perfect for FastAPI, async applications, or when running multiple searches concurrently.
|
|
49
50
|
|
|
50
51
|
```python
|
|
51
52
|
import asyncio
|
|
52
53
|
from py_youtube_search import YouTubeSearch
|
|
53
54
|
|
|
54
55
|
async def main():
|
|
55
|
-
# 1. Initialize the client (reusable)
|
|
56
|
+
# 1. Initialize the async client (reusable)
|
|
56
57
|
yt = YouTubeSearch()
|
|
57
58
|
|
|
58
59
|
# 2. Run a search
|
|
@@ -68,7 +69,30 @@ if __name__ == "__main__":
|
|
|
68
69
|
asyncio.run(main())
|
|
69
70
|
```
|
|
70
71
|
|
|
71
|
-
### 2.
|
|
72
|
+
### 2. Sync Search (Simple Scripts & Notebooks)
|
|
73
|
+
Perfect for simple scripts, Jupyter notebooks, or synchronous applications.
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from py_youtube_search import YouTubeSearchSync
|
|
77
|
+
|
|
78
|
+
def main():
|
|
79
|
+
# 1. Initialize the sync client (reusable)
|
|
80
|
+
yt = YouTubeSearchSync()
|
|
81
|
+
|
|
82
|
+
# 2. Run a search
|
|
83
|
+
videos = yt.search("Python async tutorials", limit=5)
|
|
84
|
+
|
|
85
|
+
for v in videos:
|
|
86
|
+
print(f"Title: {v['title']}")
|
|
87
|
+
print(f"Duration: {v['duration']}")
|
|
88
|
+
print(f"Views: {v['views']}")
|
|
89
|
+
print(f"Link: https://www.youtube.com/watch?v={v['id']}\n")
|
|
90
|
+
|
|
91
|
+
if __name__ == "__main__":
|
|
92
|
+
main()
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 3. Advanced Search with Filters (Async)
|
|
72
96
|
Search for specific content, like long-form videos (>20m) uploaded this week.
|
|
73
97
|
|
|
74
98
|
```python
|
|
@@ -96,6 +120,32 @@ if __name__ == "__main__":
|
|
|
96
120
|
asyncio.run(main())
|
|
97
121
|
```
|
|
98
122
|
|
|
123
|
+
### 4. Advanced Search with Filters (Sync)
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from py_youtube_search import YouTubeSearchSync, Filters
|
|
127
|
+
|
|
128
|
+
def main():
|
|
129
|
+
yt = YouTubeSearchSync()
|
|
130
|
+
|
|
131
|
+
# Search 1: Long videos about LangGraph
|
|
132
|
+
print("Searching for LangGraph...")
|
|
133
|
+
videos = yt.search("LangGraph", sp=Filters.long_this_week, limit=3)
|
|
134
|
+
|
|
135
|
+
for v in videos:
|
|
136
|
+
print(f"🎥 {v['title']} | ⏱ {v['duration']} | 👁 {v['views']}")
|
|
137
|
+
|
|
138
|
+
# Search 2: Reusing the same client for a different query
|
|
139
|
+
print("\nSearching for Python...")
|
|
140
|
+
videos_py = yt.search("Python 3.12", sp=Filters.medium_today, limit=3)
|
|
141
|
+
|
|
142
|
+
for v in videos_py:
|
|
143
|
+
print(f"🐍 {v['title']}")
|
|
144
|
+
|
|
145
|
+
if __name__ == "__main__":
|
|
146
|
+
main()
|
|
147
|
+
```
|
|
148
|
+
|
|
99
149
|
## Available Filters
|
|
100
150
|
|
|
101
151
|
Pass these constants into the `sp` parameter of the `search()` method.
|
|
@@ -118,7 +168,7 @@ Pass these constants into the `sp` parameter of the `search()` method.
|
|
|
118
168
|
|
|
119
169
|
## Data Structure
|
|
120
170
|
|
|
121
|
-
|
|
171
|
+
Both `.search()` methods return a list of dictionaries:
|
|
122
172
|
|
|
123
173
|
```json
|
|
124
174
|
[
|
|
@@ -132,8 +182,55 @@ The `.search()` method returns a list of dictionaries:
|
|
|
132
182
|
]
|
|
133
183
|
```
|
|
134
184
|
|
|
185
|
+
## API Reference
|
|
186
|
+
|
|
187
|
+
### `YouTubeSearch` (Async)
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
async def search(query: str, sp: str = None, limit: int = 15) -> list
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**Parameters:**
|
|
194
|
+
- `query` (str): The search query
|
|
195
|
+
- `sp` (str, optional): Filter string from `Filters` class
|
|
196
|
+
- `limit` (int, optional): Maximum number of results (default: 15)
|
|
197
|
+
|
|
198
|
+
**Returns:** List of video dictionaries
|
|
199
|
+
|
|
200
|
+
### `YouTubeSearchSync` (Sync)
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
def search(query: str, sp: str = None, limit: int = 15) -> list
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
**Parameters:**
|
|
207
|
+
- `query` (str): The search query
|
|
208
|
+
- `sp` (str, optional): Filter string from `Filters` class
|
|
209
|
+
- `limit` (int, optional): Maximum number of results (default: 15)
|
|
210
|
+
|
|
211
|
+
**Returns:** List of video dictionaries
|
|
212
|
+
|
|
213
|
+
## When to Use Async vs Sync?
|
|
214
|
+
|
|
215
|
+
### Use `YouTubeSearch` (Async) when:
|
|
216
|
+
- Building FastAPI, aiohttp, or other async web applications
|
|
217
|
+
- Running multiple searches concurrently
|
|
218
|
+
- Integrating with async frameworks or event loops
|
|
219
|
+
|
|
220
|
+
### Use `YouTubeSearchSync` (Sync) when:
|
|
221
|
+
- Writing simple scripts or automation tools
|
|
222
|
+
- Working in Jupyter notebooks or interactive environments
|
|
223
|
+
- Building synchronous applications (Flask, Django views, etc.)
|
|
224
|
+
|
|
135
225
|
## Dependencies
|
|
136
|
-
- `aiohttp` (for async
|
|
226
|
+
- `aiohttp>=3.8.0` (for async version)
|
|
227
|
+
- `requests>=2.25.0` (for sync version)
|
|
137
228
|
|
|
138
229
|
## License
|
|
139
230
|
MIT License. See LICENSE file for details.
|
|
231
|
+
|
|
232
|
+
## Contributing
|
|
233
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
234
|
+
|
|
235
|
+
## Issues
|
|
236
|
+
If you encounter any problems, please file an issue on GitHub.
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# py-youtube-search
|
|
2
|
+
|
|
3
|
+
A lightweight Python library to search YouTube videos programmatically without an API key.
|
|
4
|
+
It scrapes search results using regex, making it fast, robust, and perfect for both synchronous and asynchronous applications.
|
|
5
|
+
|
|
6
|
+
## Features
|
|
7
|
+
|
|
8
|
+
- **Async & Sync Support**: Choose between fully asynchronous (`aiohttp`) or synchronous (`requests`) implementations.
|
|
9
|
+
- **Reusable Client**: Create a single instance and run multiple searches with different configurations.
|
|
10
|
+
- **No API Key Required**: Search YouTube directly without setting up Google Cloud projects.
|
|
11
|
+
- **Advanced Filtering**: Built-in support for duration (Medium 3-20m, Long >20m) and upload date filters.
|
|
12
|
+
- **Rich Data Extraction**: Extracts Video ID, Title, Duration, and View Count using optimized regex.
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install py-youtube-search
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
### 1. Async Search (Recommended for Concurrent Operations)
|
|
23
|
+
Perfect for FastAPI, async applications, or when running multiple searches concurrently.
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
import asyncio
|
|
27
|
+
from py_youtube_search import YouTubeSearch
|
|
28
|
+
|
|
29
|
+
async def main():
|
|
30
|
+
# 1. Initialize the async client (reusable)
|
|
31
|
+
yt = YouTubeSearch()
|
|
32
|
+
|
|
33
|
+
# 2. Run a search
|
|
34
|
+
videos = await yt.search("Python async tutorials", limit=5)
|
|
35
|
+
|
|
36
|
+
for v in videos:
|
|
37
|
+
print(f"Title: {v['title']}")
|
|
38
|
+
print(f"Duration: {v['duration']}")
|
|
39
|
+
print(f"Views: {v['views']}")
|
|
40
|
+
print(f"Link: https://www.youtube.com/watch?v={v['id']}\n")
|
|
41
|
+
|
|
42
|
+
if __name__ == "__main__":
|
|
43
|
+
asyncio.run(main())
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 2. Sync Search (Simple Scripts & Notebooks)
|
|
47
|
+
Perfect for simple scripts, Jupyter notebooks, or synchronous applications.
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from py_youtube_search import YouTubeSearchSync
|
|
51
|
+
|
|
52
|
+
def main():
|
|
53
|
+
# 1. Initialize the sync client (reusable)
|
|
54
|
+
yt = YouTubeSearchSync()
|
|
55
|
+
|
|
56
|
+
# 2. Run a search
|
|
57
|
+
videos = yt.search("Python async tutorials", limit=5)
|
|
58
|
+
|
|
59
|
+
for v in videos:
|
|
60
|
+
print(f"Title: {v['title']}")
|
|
61
|
+
print(f"Duration: {v['duration']}")
|
|
62
|
+
print(f"Views: {v['views']}")
|
|
63
|
+
print(f"Link: https://www.youtube.com/watch?v={v['id']}\n")
|
|
64
|
+
|
|
65
|
+
if __name__ == "__main__":
|
|
66
|
+
main()
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 3. Advanced Search with Filters (Async)
|
|
70
|
+
Search for specific content, like long-form videos (>20m) uploaded this week.
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
import asyncio
|
|
74
|
+
from py_youtube_search import YouTubeSearch, Filters
|
|
75
|
+
|
|
76
|
+
async def main():
|
|
77
|
+
yt = YouTubeSearch()
|
|
78
|
+
|
|
79
|
+
# Search 1: Long videos about LangGraph
|
|
80
|
+
print("Searching for LangGraph...")
|
|
81
|
+
videos = await yt.search("LangGraph", sp=Filters.long_this_week, limit=3)
|
|
82
|
+
|
|
83
|
+
for v in videos:
|
|
84
|
+
print(f"🎥 {v['title']} | ⏱ {v['duration']} | 👁 {v['views']}")
|
|
85
|
+
|
|
86
|
+
# Search 2: Reusing the same client for a different query
|
|
87
|
+
print("\nSearching for Python...")
|
|
88
|
+
videos_py = await yt.search("Python 3.12", sp=Filters.medium_today, limit=3)
|
|
89
|
+
|
|
90
|
+
for v in videos_py:
|
|
91
|
+
print(f"🐍 {v['title']}")
|
|
92
|
+
|
|
93
|
+
if __name__ == "__main__":
|
|
94
|
+
asyncio.run(main())
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### 4. Advanced Search with Filters (Sync)
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from py_youtube_search import YouTubeSearchSync, Filters
|
|
101
|
+
|
|
102
|
+
def main():
|
|
103
|
+
yt = YouTubeSearchSync()
|
|
104
|
+
|
|
105
|
+
# Search 1: Long videos about LangGraph
|
|
106
|
+
print("Searching for LangGraph...")
|
|
107
|
+
videos = yt.search("LangGraph", sp=Filters.long_this_week, limit=3)
|
|
108
|
+
|
|
109
|
+
for v in videos:
|
|
110
|
+
print(f"🎥 {v['title']} | ⏱ {v['duration']} | 👁 {v['views']}")
|
|
111
|
+
|
|
112
|
+
# Search 2: Reusing the same client for a different query
|
|
113
|
+
print("\nSearching for Python...")
|
|
114
|
+
videos_py = yt.search("Python 3.12", sp=Filters.medium_today, limit=3)
|
|
115
|
+
|
|
116
|
+
for v in videos_py:
|
|
117
|
+
print(f"🐍 {v['title']}")
|
|
118
|
+
|
|
119
|
+
if __name__ == "__main__":
|
|
120
|
+
main()
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Available Filters
|
|
124
|
+
|
|
125
|
+
Pass these constants into the `sp` parameter of the `search()` method.
|
|
126
|
+
|
|
127
|
+
### Duration: Medium (3 - 20 Minutes)
|
|
128
|
+
| Filter Attribute | Description |
|
|
129
|
+
| :--- | :--- |
|
|
130
|
+
| `Filters.medium_today` | Uploaded **Today** |
|
|
131
|
+
| `Filters.medium_this_week` | Uploaded **This Week** |
|
|
132
|
+
| `Filters.medium_this_month` | Uploaded **This Month** |
|
|
133
|
+
| `Filters.medium_this_year` | Uploaded **This Year** |
|
|
134
|
+
|
|
135
|
+
### Duration: Long (Over 20 Minutes)
|
|
136
|
+
| Filter Attribute | Description |
|
|
137
|
+
| :--- | :--- |
|
|
138
|
+
| `Filters.long_today` | Uploaded **Today** |
|
|
139
|
+
| `Filters.long_this_week` | Uploaded **This Week** |
|
|
140
|
+
| `Filters.long_this_month` | Uploaded **This Month** |
|
|
141
|
+
| `Filters.long_this_year` | Uploaded **This Year** |
|
|
142
|
+
|
|
143
|
+
## Data Structure
|
|
144
|
+
|
|
145
|
+
Both `.search()` methods return a list of dictionaries:
|
|
146
|
+
|
|
147
|
+
```json
|
|
148
|
+
[
|
|
149
|
+
{
|
|
150
|
+
"id": "lDoYisPfcck",
|
|
151
|
+
"title": "Hack the planet! LangGraph AI HackBot Dev & Q/A",
|
|
152
|
+
"duration": "1:05:23",
|
|
153
|
+
"views": "1.2K views",
|
|
154
|
+
"url_suffix": "/watch?v=lDoYisPfcck"
|
|
155
|
+
}
|
|
156
|
+
]
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## API Reference
|
|
160
|
+
|
|
161
|
+
### `YouTubeSearch` (Async)
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
async def search(query: str, sp: str = None, limit: int = 15) -> list
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Parameters:**
|
|
168
|
+
- `query` (str): The search query
|
|
169
|
+
- `sp` (str, optional): Filter string from `Filters` class
|
|
170
|
+
- `limit` (int, optional): Maximum number of results (default: 15)
|
|
171
|
+
|
|
172
|
+
**Returns:** List of video dictionaries
|
|
173
|
+
|
|
174
|
+
### `YouTubeSearchSync` (Sync)
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
def search(query: str, sp: str = None, limit: int = 15) -> list
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**Parameters:**
|
|
181
|
+
- `query` (str): The search query
|
|
182
|
+
- `sp` (str, optional): Filter string from `Filters` class
|
|
183
|
+
- `limit` (int, optional): Maximum number of results (default: 15)
|
|
184
|
+
|
|
185
|
+
**Returns:** List of video dictionaries
|
|
186
|
+
|
|
187
|
+
## When to Use Async vs Sync?
|
|
188
|
+
|
|
189
|
+
### Use `YouTubeSearch` (Async) when:
|
|
190
|
+
- Building FastAPI, aiohttp, or other async web applications
|
|
191
|
+
- Running multiple searches concurrently
|
|
192
|
+
- Integrating with async frameworks or event loops
|
|
193
|
+
|
|
194
|
+
### Use `YouTubeSearchSync` (Sync) when:
|
|
195
|
+
- Writing simple scripts or automation tools
|
|
196
|
+
- Working in Jupyter notebooks or interactive environments
|
|
197
|
+
- Building synchronous applications (Flask, Django views, etc.)
|
|
198
|
+
|
|
199
|
+
## Dependencies
|
|
200
|
+
- `aiohttp>=3.8.0` (for async version)
|
|
201
|
+
- `requests>=2.25.0` (for sync version)
|
|
202
|
+
|
|
203
|
+
## License
|
|
204
|
+
MIT License. See LICENSE file for details.
|
|
205
|
+
|
|
206
|
+
## Contributing
|
|
207
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
208
|
+
|
|
209
|
+
## Issues
|
|
210
|
+
If you encounter any problems, please file an issue on GitHub.
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import aiohttp
|
|
3
|
+
import asyncio
|
|
4
|
+
import requests
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# Filter constants accessible to the user
|
|
8
|
+
class Filters:
|
|
9
|
+
# Duration: 3 - 20 Minutes (Medium)
|
|
10
|
+
medium_today = "EgYIAhABGAU="
|
|
11
|
+
medium_this_week = "EgQIAxGF"
|
|
12
|
+
medium_this_month = "EgYIBBABGAU="
|
|
13
|
+
medium_this_year = "EgYIBRABGAU="
|
|
14
|
+
|
|
15
|
+
# Duration: Over 20 Minutes (Long)
|
|
16
|
+
long_today = "EgYIAhABGAI="
|
|
17
|
+
long_this_week = "EgQIAxAB"
|
|
18
|
+
long_this_month = "EgYIBBABGAI="
|
|
19
|
+
long_this_year = "EgYIBRABGAI="
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class YouTubeSearch:
|
|
23
|
+
def __init__(self):
|
|
24
|
+
"""
|
|
25
|
+
Initialize the YouTubeSearch client.
|
|
26
|
+
The client is stateless; parameters are passed to the search method.
|
|
27
|
+
"""
|
|
28
|
+
self.base_url = "https://www.youtube.com/results"
|
|
29
|
+
self.headers = {
|
|
30
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async def _fetch_source(self, query: str, sp: str = None):
|
|
34
|
+
params = {"search_query": query.replace(" ", "+")}
|
|
35
|
+
if sp:
|
|
36
|
+
params["sp"] = sp
|
|
37
|
+
|
|
38
|
+
async with aiohttp.ClientSession() as session:
|
|
39
|
+
async with session.get(self.base_url, params=params, headers=self.headers) as response:
|
|
40
|
+
return await response.text()
|
|
41
|
+
|
|
42
|
+
async def search(self, query: str, sp: str = None, limit: int = 15):
|
|
43
|
+
"""
|
|
44
|
+
Asynchronously fetches and parses search results for the given query.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
query (str): The search query.
|
|
48
|
+
sp (str, optional): The filter string (use Filters class). Defaults to None.
|
|
49
|
+
limit (int, optional): Max number of results. Defaults to 15.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
list: A list of dictionaries containing video details.
|
|
53
|
+
"""
|
|
54
|
+
source = await self._fetch_source(query, sp)
|
|
55
|
+
|
|
56
|
+
# Regex to capture distinct JSON fields for ID, Title, Duration, and Views.
|
|
57
|
+
pattern = (
|
|
58
|
+
r'\"videoRenderer\":\{'
|
|
59
|
+
r'.+?\"videoId\":\"(?P<id>\S{11})\"'
|
|
60
|
+
r'.+?\"title\":\{\"runs\":\[\{\"text\":\"(?P<title>.+?)\"\}\]'
|
|
61
|
+
r'.+?\"lengthText\":\{.*?\"simpleText\":\"(?P<duration>.+?)\"\}'
|
|
62
|
+
r'.+?\"viewCountText\":\{\"simpleText\":\"(?P<views>.+?)\"\}'
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
matches = re.finditer(pattern, source)
|
|
66
|
+
|
|
67
|
+
results = []
|
|
68
|
+
for match in matches:
|
|
69
|
+
if len(results) >= limit:
|
|
70
|
+
break
|
|
71
|
+
|
|
72
|
+
data = match.groupdict()
|
|
73
|
+
results.append({
|
|
74
|
+
"id": data["id"],
|
|
75
|
+
"title": data["title"],
|
|
76
|
+
"duration": data["duration"],
|
|
77
|
+
"views": data["views"],
|
|
78
|
+
"url_suffix": f"/watch?v={data['id']}"
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
return results
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class YouTubeSearchSync:
|
|
85
|
+
def __init__(self):
|
|
86
|
+
"""
|
|
87
|
+
Initialize the YouTubeSearchSync client.
|
|
88
|
+
The client is stateless; parameters are passed to the search method.
|
|
89
|
+
"""
|
|
90
|
+
self.base_url = "https://www.youtube.com/results"
|
|
91
|
+
self.headers = {
|
|
92
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
def _fetch_source(self, query: str, sp: str = None):
|
|
96
|
+
params = {"search_query": query.replace(" ", "+")}
|
|
97
|
+
if sp:
|
|
98
|
+
params["sp"] = sp
|
|
99
|
+
|
|
100
|
+
response = requests.get(self.base_url, params=params, headers=self.headers)
|
|
101
|
+
return response.text
|
|
102
|
+
|
|
103
|
+
def search(self, query: str, sp: str = None, limit: int = 15):
|
|
104
|
+
"""
|
|
105
|
+
Synchronously fetches and parses search results for the given query.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
query (str): The search query.
|
|
109
|
+
sp (str, optional): The filter string (use Filters class). Defaults to None.
|
|
110
|
+
limit (int, optional): Max number of results. Defaults to 15.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
list: A list of dictionaries containing video details.
|
|
114
|
+
"""
|
|
115
|
+
source = self._fetch_source(query, sp)
|
|
116
|
+
|
|
117
|
+
# Regex to capture distinct JSON fields for ID, Title, Duration, and Views.
|
|
118
|
+
pattern = (
|
|
119
|
+
r'\"videoRenderer\":\{'
|
|
120
|
+
r'.+?\"videoId\":\"(?P<id>\S{11})\"'
|
|
121
|
+
r'.+?\"title\":\{\"runs\":\[\{\"text\":\"(?P<title>.+?)\"\}\]'
|
|
122
|
+
r'.+?\"lengthText\":\{.*?\"simpleText\":\"(?P<duration>.+?)\"\}'
|
|
123
|
+
r'.+?\"viewCountText\":\{\"simpleText\":\"(?P<views>.+?)\"\}'
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
matches = re.finditer(pattern, source)
|
|
127
|
+
|
|
128
|
+
results = []
|
|
129
|
+
for match in matches:
|
|
130
|
+
if len(results) >= limit:
|
|
131
|
+
break
|
|
132
|
+
|
|
133
|
+
data = match.groupdict()
|
|
134
|
+
results.append({
|
|
135
|
+
"id": data["id"],
|
|
136
|
+
"title": data["title"],
|
|
137
|
+
"duration": data["duration"],
|
|
138
|
+
"views": data["views"],
|
|
139
|
+
"url_suffix": f"/watch?v={data['id']}"
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
return results
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# --- Usage Example (Async) ---
|
|
146
|
+
# import asyncio
|
|
147
|
+
# async def main():
|
|
148
|
+
# yt = YouTubeSearch()
|
|
149
|
+
#
|
|
150
|
+
# # Search 1: Long videos about LangGraph
|
|
151
|
+
# print("Searching LangGraph...")
|
|
152
|
+
# videos1 = await yt.search("LangGraph", sp=Filters.long_this_week, limit=5)
|
|
153
|
+
# print(f"Found {len(videos1)} videos")
|
|
154
|
+
#
|
|
155
|
+
# # Search 2: Short Python tutorials (reusing the same instance)
|
|
156
|
+
# print("Searching Python...")
|
|
157
|
+
# videos2 = await yt.search("Python", sp=Filters.medium_today, limit=3)
|
|
158
|
+
# print(f"Found {len(videos2)} videos")
|
|
159
|
+
#
|
|
160
|
+
# asyncio.run(main())
|
|
161
|
+
|
|
162
|
+
# --- Usage Example (Sync) ---
|
|
163
|
+
# def main():
|
|
164
|
+
# yt = YouTubeSearchSync()
|
|
165
|
+
#
|
|
166
|
+
# # Search 1: Long videos about LangGraph
|
|
167
|
+
# print("Searching LangGraph...")
|
|
168
|
+
# videos1 = yt.search("LangGraph", sp=Filters.long_this_week, limit=5)
|
|
169
|
+
# print(f"Found {len(videos1)} videos")
|
|
170
|
+
#
|
|
171
|
+
# # Search 2: Short Python tutorials (reusing the same instance)
|
|
172
|
+
# print("Searching Python...")
|
|
173
|
+
# videos2 = yt.search("Python", sp=Filters.medium_today, limit=3)
|
|
174
|
+
# print(f"Found {len(videos2)} videos")
|
|
175
|
+
#
|
|
176
|
+
# main()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: py-youtube-search
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: A lightweight, regex-based YouTube search library without API keys.
|
|
5
5
|
Home-page: https://github.com/VishvaRam/py-youtube-search
|
|
6
6
|
Author: VishvaRam
|
|
@@ -12,6 +12,7 @@ Classifier: Operating System :: OS Independent
|
|
|
12
12
|
Requires-Python: >=3.6
|
|
13
13
|
Description-Content-Type: text/markdown
|
|
14
14
|
Requires-Dist: aiohttp>=3.8.0
|
|
15
|
+
Requires-Dist: requests>=2.25.0
|
|
15
16
|
Dynamic: author
|
|
16
17
|
Dynamic: author-email
|
|
17
18
|
Dynamic: classifier
|
|
@@ -25,12 +26,12 @@ Dynamic: summary
|
|
|
25
26
|
|
|
26
27
|
# py-youtube-search
|
|
27
28
|
|
|
28
|
-
A lightweight
|
|
29
|
-
It scrapes search results using
|
|
29
|
+
A lightweight Python library to search YouTube videos programmatically without an API key.
|
|
30
|
+
It scrapes search results using regex, making it fast, robust, and perfect for both synchronous and asynchronous applications.
|
|
30
31
|
|
|
31
32
|
## Features
|
|
32
33
|
|
|
33
|
-
- **Async Support**:
|
|
34
|
+
- **Async & Sync Support**: Choose between fully asynchronous (`aiohttp`) or synchronous (`requests`) implementations.
|
|
34
35
|
- **Reusable Client**: Create a single instance and run multiple searches with different configurations.
|
|
35
36
|
- **No API Key Required**: Search YouTube directly without setting up Google Cloud projects.
|
|
36
37
|
- **Advanced Filtering**: Built-in support for duration (Medium 3-20m, Long >20m) and upload date filters.
|
|
@@ -44,15 +45,15 @@ pip install py-youtube-search
|
|
|
44
45
|
|
|
45
46
|
## Quick Start
|
|
46
47
|
|
|
47
|
-
### 1.
|
|
48
|
-
|
|
48
|
+
### 1. Async Search (Recommended for Concurrent Operations)
|
|
49
|
+
Perfect for FastAPI, async applications, or when running multiple searches concurrently.
|
|
49
50
|
|
|
50
51
|
```python
|
|
51
52
|
import asyncio
|
|
52
53
|
from py_youtube_search import YouTubeSearch
|
|
53
54
|
|
|
54
55
|
async def main():
|
|
55
|
-
# 1. Initialize the client (reusable)
|
|
56
|
+
# 1. Initialize the async client (reusable)
|
|
56
57
|
yt = YouTubeSearch()
|
|
57
58
|
|
|
58
59
|
# 2. Run a search
|
|
@@ -68,7 +69,30 @@ if __name__ == "__main__":
|
|
|
68
69
|
asyncio.run(main())
|
|
69
70
|
```
|
|
70
71
|
|
|
71
|
-
### 2.
|
|
72
|
+
### 2. Sync Search (Simple Scripts & Notebooks)
|
|
73
|
+
Perfect for simple scripts, Jupyter notebooks, or synchronous applications.
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from py_youtube_search import YouTubeSearchSync
|
|
77
|
+
|
|
78
|
+
def main():
|
|
79
|
+
# 1. Initialize the sync client (reusable)
|
|
80
|
+
yt = YouTubeSearchSync()
|
|
81
|
+
|
|
82
|
+
# 2. Run a search
|
|
83
|
+
videos = yt.search("Python async tutorials", limit=5)
|
|
84
|
+
|
|
85
|
+
for v in videos:
|
|
86
|
+
print(f"Title: {v['title']}")
|
|
87
|
+
print(f"Duration: {v['duration']}")
|
|
88
|
+
print(f"Views: {v['views']}")
|
|
89
|
+
print(f"Link: https://www.youtube.com/watch?v={v['id']}\n")
|
|
90
|
+
|
|
91
|
+
if __name__ == "__main__":
|
|
92
|
+
main()
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 3. Advanced Search with Filters (Async)
|
|
72
96
|
Search for specific content, like long-form videos (>20m) uploaded this week.
|
|
73
97
|
|
|
74
98
|
```python
|
|
@@ -96,6 +120,32 @@ if __name__ == "__main__":
|
|
|
96
120
|
asyncio.run(main())
|
|
97
121
|
```
|
|
98
122
|
|
|
123
|
+
### 4. Advanced Search with Filters (Sync)
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from py_youtube_search import YouTubeSearchSync, Filters
|
|
127
|
+
|
|
128
|
+
def main():
|
|
129
|
+
yt = YouTubeSearchSync()
|
|
130
|
+
|
|
131
|
+
# Search 1: Long videos about LangGraph
|
|
132
|
+
print("Searching for LangGraph...")
|
|
133
|
+
videos = yt.search("LangGraph", sp=Filters.long_this_week, limit=3)
|
|
134
|
+
|
|
135
|
+
for v in videos:
|
|
136
|
+
print(f"🎥 {v['title']} | ⏱ {v['duration']} | 👁 {v['views']}")
|
|
137
|
+
|
|
138
|
+
# Search 2: Reusing the same client for a different query
|
|
139
|
+
print("\nSearching for Python...")
|
|
140
|
+
videos_py = yt.search("Python 3.12", sp=Filters.medium_today, limit=3)
|
|
141
|
+
|
|
142
|
+
for v in videos_py:
|
|
143
|
+
print(f"🐍 {v['title']}")
|
|
144
|
+
|
|
145
|
+
if __name__ == "__main__":
|
|
146
|
+
main()
|
|
147
|
+
```
|
|
148
|
+
|
|
99
149
|
## Available Filters
|
|
100
150
|
|
|
101
151
|
Pass these constants into the `sp` parameter of the `search()` method.
|
|
@@ -118,7 +168,7 @@ Pass these constants into the `sp` parameter of the `search()` method.
|
|
|
118
168
|
|
|
119
169
|
## Data Structure
|
|
120
170
|
|
|
121
|
-
|
|
171
|
+
Both `.search()` methods return a list of dictionaries:
|
|
122
172
|
|
|
123
173
|
```json
|
|
124
174
|
[
|
|
@@ -132,8 +182,55 @@ The `.search()` method returns a list of dictionaries:
|
|
|
132
182
|
]
|
|
133
183
|
```
|
|
134
184
|
|
|
185
|
+
## API Reference
|
|
186
|
+
|
|
187
|
+
### `YouTubeSearch` (Async)
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
async def search(query: str, sp: str = None, limit: int = 15) -> list
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**Parameters:**
|
|
194
|
+
- `query` (str): The search query
|
|
195
|
+
- `sp` (str, optional): Filter string from `Filters` class
|
|
196
|
+
- `limit` (int, optional): Maximum number of results (default: 15)
|
|
197
|
+
|
|
198
|
+
**Returns:** List of video dictionaries
|
|
199
|
+
|
|
200
|
+
### `YouTubeSearchSync` (Sync)
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
def search(query: str, sp: str = None, limit: int = 15) -> list
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
**Parameters:**
|
|
207
|
+
- `query` (str): The search query
|
|
208
|
+
- `sp` (str, optional): Filter string from `Filters` class
|
|
209
|
+
- `limit` (int, optional): Maximum number of results (default: 15)
|
|
210
|
+
|
|
211
|
+
**Returns:** List of video dictionaries
|
|
212
|
+
|
|
213
|
+
## When to Use Async vs Sync?
|
|
214
|
+
|
|
215
|
+
### Use `YouTubeSearch` (Async) when:
|
|
216
|
+
- Building FastAPI, aiohttp, or other async web applications
|
|
217
|
+
- Running multiple searches concurrently
|
|
218
|
+
- Integrating with async frameworks or event loops
|
|
219
|
+
|
|
220
|
+
### Use `YouTubeSearchSync` (Sync) when:
|
|
221
|
+
- Writing simple scripts or automation tools
|
|
222
|
+
- Working in Jupyter notebooks or interactive environments
|
|
223
|
+
- Building synchronous applications (Flask, Django views, etc.)
|
|
224
|
+
|
|
135
225
|
## Dependencies
|
|
136
|
-
- `aiohttp` (for async
|
|
226
|
+
- `aiohttp>=3.8.0` (for async version)
|
|
227
|
+
- `requests>=2.25.0` (for sync version)
|
|
137
228
|
|
|
138
229
|
## License
|
|
139
230
|
MIT License. See LICENSE file for details.
|
|
231
|
+
|
|
232
|
+
## Contributing
|
|
233
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
234
|
+
|
|
235
|
+
## Issues
|
|
236
|
+
If you encounter any problems, please file an issue on GitHub.
|
|
@@ -5,10 +5,11 @@ with open("README.md", "r", encoding="utf-8") as fh:
|
|
|
5
5
|
|
|
6
6
|
setup(
|
|
7
7
|
name="py-youtube-search",
|
|
8
|
-
version="0.2.
|
|
8
|
+
version="0.2.4",
|
|
9
9
|
author="VishvaRam",
|
|
10
10
|
install_requires=[
|
|
11
11
|
"aiohttp>=3.8.0",
|
|
12
|
+
"requests>=2.25.0",
|
|
12
13
|
],
|
|
13
14
|
author_email="murthyvishva@gmail.com",
|
|
14
15
|
description="A lightweight, regex-based YouTube search library without API keys.",
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
# py-youtube-search
|
|
2
|
-
|
|
3
|
-
A lightweight, asynchronous Python library to search YouTube videos programmatically without an API key.
|
|
4
|
-
It scrapes search results using `aiohttp` and `re`, making it fast, robust, and perfect for high-performance applications.
|
|
5
|
-
|
|
6
|
-
## Features
|
|
7
|
-
|
|
8
|
-
- **Async Support**: Fully asynchronous using `aiohttp` for non-blocking execution.
|
|
9
|
-
- **Reusable Client**: Create a single instance and run multiple searches with different configurations.
|
|
10
|
-
- **No API Key Required**: Search YouTube directly without setting up Google Cloud projects.
|
|
11
|
-
- **Advanced Filtering**: Built-in support for duration (Medium 3-20m, Long >20m) and upload date filters.
|
|
12
|
-
- **Rich Data Extraction**: Extracts Video ID, Title, Duration, and View Count using optimized regex.
|
|
13
|
-
|
|
14
|
-
## Installation
|
|
15
|
-
|
|
16
|
-
```bash
|
|
17
|
-
pip install py-youtube-search
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
## Quick Start
|
|
21
|
-
|
|
22
|
-
### 1. Basic Async Search
|
|
23
|
-
Initialize the client once and run multiple searches.
|
|
24
|
-
|
|
25
|
-
```python
|
|
26
|
-
import asyncio
|
|
27
|
-
from py_youtube_search import YouTubeSearch
|
|
28
|
-
|
|
29
|
-
async def main():
|
|
30
|
-
# 1. Initialize the client (reusable)
|
|
31
|
-
yt = YouTubeSearch()
|
|
32
|
-
|
|
33
|
-
# 2. Run a search
|
|
34
|
-
videos = await yt.search("Python async tutorials", limit=5)
|
|
35
|
-
|
|
36
|
-
for v in videos:
|
|
37
|
-
print(f"Title: {v['title']}")
|
|
38
|
-
print(f"Duration: {v['duration']}")
|
|
39
|
-
print(f"Views: {v['views']}")
|
|
40
|
-
print(f"Link: https://www.youtube.com/watch?v={v['id']}\n")
|
|
41
|
-
|
|
42
|
-
if __name__ == "__main__":
|
|
43
|
-
asyncio.run(main())
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
### 2. Advanced Search with Filters
|
|
47
|
-
Search for specific content, like long-form videos (>20m) uploaded this week.
|
|
48
|
-
|
|
49
|
-
```python
|
|
50
|
-
import asyncio
|
|
51
|
-
from py_youtube_search import YouTubeSearch, Filters
|
|
52
|
-
|
|
53
|
-
async def main():
|
|
54
|
-
yt = YouTubeSearch()
|
|
55
|
-
|
|
56
|
-
# Search 1: Long videos about LangGraph
|
|
57
|
-
print("Searching for LangGraph...")
|
|
58
|
-
videos = await yt.search("LangGraph", sp=Filters.long_this_week, limit=3)
|
|
59
|
-
|
|
60
|
-
for v in videos:
|
|
61
|
-
print(f"🎥 {v['title']} | ⏱ {v['duration']} | 👁 {v['views']}")
|
|
62
|
-
|
|
63
|
-
# Search 2: Reusing the same client for a different query
|
|
64
|
-
print("\nSearching for Python...")
|
|
65
|
-
videos_py = await yt.search("Python 3.12", sp=Filters.medium_today, limit=3)
|
|
66
|
-
|
|
67
|
-
for v in videos_py:
|
|
68
|
-
print(f"🐍 {v['title']}")
|
|
69
|
-
|
|
70
|
-
if __name__ == "__main__":
|
|
71
|
-
asyncio.run(main())
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
## Available Filters
|
|
75
|
-
|
|
76
|
-
Pass these constants into the `sp` parameter of the `search()` method.
|
|
77
|
-
|
|
78
|
-
### Duration: Medium (3 - 20 Minutes)
|
|
79
|
-
| Filter Attribute | Description |
|
|
80
|
-
| :--- | :--- |
|
|
81
|
-
| `Filters.medium_today` | Uploaded **Today** |
|
|
82
|
-
| `Filters.medium_this_week` | Uploaded **This Week** |
|
|
83
|
-
| `Filters.medium_this_month` | Uploaded **This Month** |
|
|
84
|
-
| `Filters.medium_this_year` | Uploaded **This Year** |
|
|
85
|
-
|
|
86
|
-
### Duration: Long (Over 20 Minutes)
|
|
87
|
-
| Filter Attribute | Description |
|
|
88
|
-
| :--- | :--- |
|
|
89
|
-
| `Filters.long_today` | Uploaded **Today** |
|
|
90
|
-
| `Filters.long_this_week` | Uploaded **This Week** |
|
|
91
|
-
| `Filters.long_this_month` | Uploaded **This Month** |
|
|
92
|
-
| `Filters.long_this_year` | Uploaded **This Year** |
|
|
93
|
-
|
|
94
|
-
## Data Structure
|
|
95
|
-
|
|
96
|
-
The `.search()` method returns a list of dictionaries:
|
|
97
|
-
|
|
98
|
-
```json
|
|
99
|
-
[
|
|
100
|
-
{
|
|
101
|
-
"id": "lDoYisPfcck",
|
|
102
|
-
"title": "Hack the planet! LangGraph AI HackBot Dev & Q/A",
|
|
103
|
-
"duration": "1:05:23",
|
|
104
|
-
"views": "1.2K views",
|
|
105
|
-
"url_suffix": "/watch?v=lDoYisPfcck"
|
|
106
|
-
}
|
|
107
|
-
]
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
## Dependencies
|
|
111
|
-
- `aiohttp` (for async requests)
|
|
112
|
-
|
|
113
|
-
## License
|
|
114
|
-
MIT License. See LICENSE file for details.
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import re
|
|
2
|
-
import aiohttp
|
|
3
|
-
import asyncio
|
|
4
|
-
|
|
5
|
-
# Filter constants accessible to the user
|
|
6
|
-
class Filters:
|
|
7
|
-
# Duration: 3 - 20 Minutes (Medium)
|
|
8
|
-
medium_today = "EgYIAhABGAU="
|
|
9
|
-
medium_this_week = "EgQIAxGF"
|
|
10
|
-
medium_this_month = "EgYIBBABGAU="
|
|
11
|
-
medium_this_year = "EgYIBRABGAU="
|
|
12
|
-
|
|
13
|
-
# Duration: Over 20 Minutes (Long)
|
|
14
|
-
long_today = "EgYIAhABGAI="
|
|
15
|
-
long_this_week = "EgQIAxAB"
|
|
16
|
-
long_this_month = "EgYIBBABGAI="
|
|
17
|
-
long_this_year = "EgYIBRABGAI="
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class YouTubeSearch:
|
|
21
|
-
def __init__(self):
|
|
22
|
-
"""
|
|
23
|
-
Initialize the YouTubeSearch client.
|
|
24
|
-
The client is stateless; parameters are passed to the search method.
|
|
25
|
-
"""
|
|
26
|
-
self.base_url = "https://www.youtube.com/results"
|
|
27
|
-
self.headers = {
|
|
28
|
-
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async def _fetch_source(self, keywords: str, sp: str = None):
|
|
32
|
-
params = {"search_query": keywords.replace(" ", "+")}
|
|
33
|
-
if sp:
|
|
34
|
-
params["sp"] = sp
|
|
35
|
-
|
|
36
|
-
async with aiohttp.ClientSession() as session:
|
|
37
|
-
async with session.get(self.base_url, params=params, headers=self.headers) as response:
|
|
38
|
-
return await response.text()
|
|
39
|
-
|
|
40
|
-
async def search(self, keywords: str, sp: str = None, limit: int = 15):
|
|
41
|
-
"""
|
|
42
|
-
Asynchronously fetches and parses search results for the given keywords.
|
|
43
|
-
|
|
44
|
-
Args:
|
|
45
|
-
keywords (str): The search query.
|
|
46
|
-
sp (str, optional): The filter string (use Filters class). Defaults to None.
|
|
47
|
-
limit (int, optional): Max number of results. Defaults to 15.
|
|
48
|
-
|
|
49
|
-
Returns:
|
|
50
|
-
list: A list of dictionaries containing video details.
|
|
51
|
-
"""
|
|
52
|
-
source = await self._fetch_source(keywords, sp)
|
|
53
|
-
|
|
54
|
-
# Regex to capture distinct JSON fields for ID, Title, Duration, and Views.
|
|
55
|
-
pattern = (
|
|
56
|
-
r'\"videoRenderer\":\{'
|
|
57
|
-
r'.+?\"videoId\":\"(?P<id>\S{11})\"'
|
|
58
|
-
r'.+?\"title\":\{\"runs\":\[\{\"text\":\"(?P<title>.+?)\"\}\]'
|
|
59
|
-
r'.+?\"lengthText\":\{.*?\"simpleText\":\"(?P<duration>.+?)\"\}'
|
|
60
|
-
r'.+?\"viewCountText\":\{\"simpleText\":\"(?P<views>.+?)\"\}'
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
matches = re.finditer(pattern, source)
|
|
64
|
-
|
|
65
|
-
results = []
|
|
66
|
-
for match in matches:
|
|
67
|
-
if len(results) >= limit:
|
|
68
|
-
break
|
|
69
|
-
|
|
70
|
-
data = match.groupdict()
|
|
71
|
-
results.append({
|
|
72
|
-
"id": data["id"],
|
|
73
|
-
"title": data["title"],
|
|
74
|
-
"duration": data["duration"],
|
|
75
|
-
"views": data["views"],
|
|
76
|
-
"url_suffix": f"/watch?v={data['id']}"
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
return results
|
|
80
|
-
|
|
81
|
-
# --- Usage Example (Async) ---
|
|
82
|
-
# import asyncio
|
|
83
|
-
# async def main():
|
|
84
|
-
# yt = YouTubeSearch()
|
|
85
|
-
#
|
|
86
|
-
# # Search 1: Long videos about LangGraph
|
|
87
|
-
# print("Searching LangGraph...")
|
|
88
|
-
# videos1 = await yt.search("LangGraph", sp=Filters.long_this_week, limit=5)
|
|
89
|
-
# print(f"Found {len(videos1)} videos")
|
|
90
|
-
#
|
|
91
|
-
# # Search 2: Short Python tutorials (reusing the same instance)
|
|
92
|
-
# print("Searching Python...")
|
|
93
|
-
# videos2 = await yt.search("Python", sp=Filters.medium_today, limit=3)
|
|
94
|
-
# print(f"Found {len(videos2)} videos")
|
|
95
|
-
#
|
|
96
|
-
# asyncio.run(main())
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
aiohttp>=3.8.0
|
|
File without changes
|
{py_youtube_search-0.2.2 → py_youtube_search-0.2.4}/py_youtube_search.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{py_youtube_search-0.2.2 → py_youtube_search-0.2.4}/py_youtube_search.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|