py-youtube-search 0.2.4
A lightweight, regex-based YouTube search library without API keys.
License: MIT
Requires Python: >=3.6

# py-youtube-search

A lightweight Python library to search YouTube videos programmatically without an API key. 
It scrapes search results using regex, making it fast, robust, and perfect for both synchronous and asynchronous applications.

## Features

- **Async & Sync Support**: Choose between fully asynchronous (`aiohttp`) or synchronous (`requests`) implementations.
- **Reusable Client**: Create a single instance and run multiple searches with different configurations.
- **No API Key Required**: Search YouTube directly without setting up Google Cloud projects.
- **Advanced Filtering**: Built-in support for duration (Medium 3-20m, Long >20m) and upload date filters.
- **Rich Data Extraction**: Extracts Video ID, Title, Duration, and View Count using optimized regex.

## Installation

```bash
pip install py-youtube-search
```

## Quick Start

### 1. Async Search (Recommended for Concurrent Operations)
Perfect for FastAPI, async applications, or when running multiple searches concurrently.

```python
import asyncio
from py_youtube_search import YouTubeSearch

async def main():
    # 1. Initialize the async client (reusable)
    yt = YouTubeSearch()
    
    # 2. Run a search
    videos = await yt.search("Python async tutorials", limit=5)

    for v in videos:
        print(f"Title: {v['title']}")
        print(f"Duration: {v['duration']}")
        print(f"Views: {v['views']}")
        print(f"Link: https://www.youtube.com/watch?v={v['id']}\n")

if __name__ == "__main__":
    asyncio.run(main())
```

### 2. Sync Search (Simple Scripts & Notebooks)
Perfect for simple scripts, Jupyter notebooks, or synchronous applications.

```python
from py_youtube_search import YouTubeSearchSync

def main():
    # 1. Initialize the sync client (reusable)
    yt = YouTubeSearchSync()
    
    # 2. Run a search
    videos = yt.search("Python async tutorials", limit=5)

    for v in videos:
        print(f"Title: {v['title']}")
        print(f"Duration: {v['duration']}")
        print(f"Views: {v['views']}")
        print(f"Link: https://www.youtube.com/watch?v={v['id']}\n")

if __name__ == "__main__":
    main()
```

### 3. Advanced Search with Filters (Async)
Search for specific content, like long-form videos (>20m) uploaded this week.

```python
import asyncio
from py_youtube_search import YouTubeSearch, Filters

async def main():
    yt = YouTubeSearch()

    # Search 1: Long videos about LangGraph
    print("Searching for LangGraph...")
    videos = await yt.search("LangGraph", sp=Filters.long_this_week, limit=3)

    for v in videos:
        print(f"🎥 {v['title']} | ⏱ {v['duration']} | 👁 {v['views']}")

    # Search 2: Reusing the same client for a different query
    print("\nSearching for Python...")
    videos_py = await yt.search("Python 3.12", sp=Filters.medium_today, limit=3)
    
    for v in videos_py:
        print(f"🐍 {v['title']}")

if __name__ == "__main__":
    asyncio.run(main())
```

### 4. Advanced Search with Filters (Sync)

```python
from py_youtube_search import YouTubeSearchSync, Filters

def main():
    yt = YouTubeSearchSync()

    # Search 1: Long videos about LangGraph
    print("Searching for LangGraph...")
    videos = yt.search("LangGraph", sp=Filters.long_this_week, limit=3)

    for v in videos:
        print(f"🎥 {v['title']} | ⏱ {v['duration']} | 👁 {v['views']}")

    # Search 2: Reusing the same client for a different query
    print("\nSearching for Python...")
    videos_py = yt.search("Python 3.12", sp=Filters.medium_today, limit=3)
    
    for v in videos_py:
        print(f"🐍 {v['title']}")

if __name__ == "__main__":
    main()
```

## Available Filters

Pass these constants into the `sp` parameter of the `search()` method.

### Duration: Medium (3 - 20 Minutes)
| Filter Attribute | Description |
| :--- | :--- |
| `Filters.medium_today` | Uploaded **Today** |
| `Filters.medium_this_week` | Uploaded **This Week** |
| `Filters.medium_this_month` | Uploaded **This Month** |
| `Filters.medium_this_year` | Uploaded **This Year** |

### Duration: Long (Over 20 Minutes)
| Filter Attribute | Description |
| :--- | :--- |
| `Filters.long_today` | Uploaded **Today** |
| `Filters.long_this_week` | Uploaded **This Week** |
| `Filters.long_this_month` | Uploaded **This Month** |
| `Filters.long_this_year` | Uploaded **This Year** |

## Data Structure

Both `.search()` methods return a list of dictionaries:

```json
[
  {
    "id": "lDoYisPfcck",
    "title": "Hack the planet! LangGraph AI HackBot Dev & Q/A",
    "duration": "1:05:23",
    "views": "1.2K views",
    "url_suffix": "/watch?v=lDoYisPfcck"
  }
]
```

## API Reference

### `YouTubeSearch` (Async)

```python
async def search(query: str, sp: str = None, limit: int = 15) -> list
```

**Parameters:**
- `query` (str): The search query
- `sp` (str, optional): Filter string from `Filters` class
- `limit` (int, optional): Maximum number of results (default: 15)

**Returns:** List of video dictionaries

### `YouTubeSearchSync` (Sync)

```python
def search(query: str, sp: str = None, limit: int = 15) -> list
```

**Parameters:**
- `query` (str): The search query
- `sp` (str, optional): Filter string from `Filters` class
- `limit` (int, optional): Maximum number of results (default: 15)

**Returns:** List of video dictionaries

## When to Use Async vs Sync?

### Use `YouTubeSearch` (Async) when:
- Building FastAPI, aiohttp, or other async web applications
- Running multiple searches concurrently
- Integrating with async frameworks or event loops

### Use `YouTubeSearchSync` (Sync) when:
- Writing simple scripts or automation tools
- Working in Jupyter notebooks or interactive environments
- Building synchronous applications (Flask, Django views, etc.)

## Dependencies
- `aiohttp>=3.8.0` (for async version)
- `requests>=2.25.0` (for sync version)

## License
MIT License. See LICENSE file for details.

## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.

## Issues
If you encounter any problems, please file an issue on GitHub.