|
| 1 | +import asyncio |
| 2 | +from collections.abc import AsyncIterator |
| 3 | +from contextlib import asynccontextmanager |
| 4 | +from typing import TypedDict |
| 5 | + |
| 6 | +from fastapi import FastAPI |
| 7 | + |
| 8 | +from crawlee.crawlers import ParselCrawler, ParselCrawlingContext |
| 9 | + |
| 10 | + |
| 11 | +class State(TypedDict): |
| 12 | + """State available in the app.""" |
| 13 | + |
| 14 | + crawler: ParselCrawler |
| 15 | + requests_to_results: dict[str, asyncio.Future[dict[str, str]]] |
| 16 | + |
| 17 | + |
| 18 | +@asynccontextmanager |
| 19 | +async def lifespan(app: FastAPI) -> AsyncIterator[State]: |
| 20 | + # Start up code that runs once when the app starts |
| 21 | + |
| 22 | + # Results will be stored in this dictionary |
| 23 | + requests_to_results = dict[str, asyncio.Future[dict[str, str]]]() |
| 24 | + |
| 25 | + crawler = ParselCrawler( |
| 26 | + # Keep the crawler alive even when there are no more requests to process now. |
| 27 | + # This makes the crawler wait for more requests to be added later. |
| 28 | + keep_alive=True |
| 29 | + ) |
| 30 | + |
| 31 | + # Define the default request handler, which will be called for every request. |
| 32 | + @crawler.router.default_handler |
| 33 | + async def request_handler(context: ParselCrawlingContext) -> None: |
| 34 | + context.log.info(f'Processing {context.request.url} ...') |
| 35 | + title = context.selector.xpath('//title/text()').get() or '' |
| 36 | + |
| 37 | + # Extract data from the page and save it to the result dictionary. |
| 38 | + requests_to_results[context.request.unique_key].set_result( |
| 39 | + { |
| 40 | + 'title': title, |
| 41 | + } |
| 42 | + ) |
| 43 | + |
| 44 | + # Start the crawler without awaiting it to finish |
| 45 | + crawler.log.info(f'Starting crawler for the {app.title}') |
| 46 | + run_task = asyncio.create_task(crawler.run([])) |
| 47 | + |
| 48 | + # Make the crawler and the result dictionary available in the app state |
| 49 | + yield {'crawler': crawler, 'requests_to_results': requests_to_results} |
| 50 | + |
| 51 | + # Cleanup code that runs once when the app shuts down |
| 52 | + crawler.stop() |
| 53 | + # Wait for the crawler to finish |
| 54 | + await run_task |
0 commit comments