firecrawl 3.4.0__py3-none-any.whl → 4.1.0__py3-none-any.whl

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.

Potentially problematic release.


This version of firecrawl might be problematic. Click here for more details.

@@ -3,12 +3,12 @@ Crawling functionality for Firecrawl v2 API.
3
3
  """
4
4
 
5
5
  import time
6
- from typing import Optional, Dict, Any
6
+ from typing import Optional, Dict, Any, List
7
7
  from ..types import (
8
8
  CrawlRequest,
9
9
  CrawlJob,
10
10
  CrawlResponse, Document, CrawlParamsRequest, CrawlParamsResponse, CrawlParamsData,
11
- WebhookConfig, CrawlErrorsResponse, ActiveCrawlsResponse, ActiveCrawl
11
+ WebhookConfig, CrawlErrorsResponse, ActiveCrawlsResponse, ActiveCrawl, PaginationConfig
12
12
  )
13
13
  from ..utils import HttpClient, handle_response_error, validate_scrape_options, prepare_scrape_options
14
14
  from ..utils.normalize import normalize_document_input
@@ -138,13 +138,18 @@ def start_crawl(client: HttpClient, request: CrawlRequest) -> CrawlResponse:
138
138
  raise Exception(response_data.get("error", "Unknown error occurred"))
139
139
 
140
140
 
141
- def get_crawl_status(client: HttpClient, job_id: str) -> CrawlJob:
141
+ def get_crawl_status(
142
+ client: HttpClient,
143
+ job_id: str,
144
+ pagination_config: Optional[PaginationConfig] = None
145
+ ) -> CrawlJob:
142
146
  """
143
147
  Get the status of a crawl job.
144
148
 
145
149
  Args:
146
150
  client: HTTP client instance
147
151
  job_id: ID of the crawl job
152
+ pagination_config: Optional configuration for pagination behavior
148
153
 
149
154
  Returns:
150
155
  CrawlJob with current status and data
@@ -176,6 +181,16 @@ def get_crawl_status(client: HttpClient, job_id: str) -> CrawlJob:
176
181
  else:
177
182
  documents.append(Document(**normalize_document_input(doc_data)))
178
183
 
184
+ # Handle pagination if requested
185
+ auto_paginate = pagination_config.auto_paginate if pagination_config else True
186
+ if auto_paginate and response_data.get("next") and not (pagination_config and pagination_config.max_results is not None and len(documents) >= pagination_config.max_results):
187
+ documents = _fetch_all_pages(
188
+ client,
189
+ response_data.get("next"),
190
+ documents,
191
+ pagination_config
192
+ )
193
+
179
194
  # Create CrawlJob with current status and data
180
195
  return CrawlJob(
181
196
  status=response_data.get("status"),
@@ -183,13 +198,87 @@ def get_crawl_status(client: HttpClient, job_id: str) -> CrawlJob:
183
198
  total=response_data.get("total", 0),
184
199
  credits_used=response_data.get("creditsUsed", 0),
185
200
  expires_at=response_data.get("expiresAt"),
186
- next=response_data.get("next", None),
201
+ next=response_data.get("next", None) if not auto_paginate else None,
187
202
  data=documents
188
203
  )
189
204
  else:
190
205
  raise Exception(response_data.get("error", "Unknown error occurred"))
191
206
 
192
207
 
208
+ def _fetch_all_pages(
209
+ client: HttpClient,
210
+ next_url: str,
211
+ initial_documents: List[Document],
212
+ pagination_config: Optional[PaginationConfig] = None
213
+ ) -> List[Document]:
214
+ """
215
+ Fetch all pages of crawl results.
216
+
217
+ Args:
218
+ client: HTTP client instance
219
+ next_url: URL for the next page
220
+ initial_documents: Documents from the first page
221
+ pagination_config: Optional configuration for pagination limits
222
+
223
+ Returns:
224
+ List of all documents from all pages
225
+ """
226
+ documents = initial_documents.copy()
227
+ current_url = next_url
228
+ page_count = 0
229
+
230
+ # Apply pagination limits
231
+ max_pages = pagination_config.max_pages if pagination_config else None
232
+ max_results = pagination_config.max_results if pagination_config else None
233
+ max_wait_time = pagination_config.max_wait_time if pagination_config else None
234
+
235
+ start_time = time.monotonic()
236
+
237
+ while current_url:
238
+ # Check pagination limits (treat 0 as a valid limit)
239
+ if (max_pages is not None) and page_count >= max_pages:
240
+ break
241
+
242
+ if (max_wait_time is not None) and (time.monotonic() - start_time) > max_wait_time:
243
+ break
244
+
245
+ # Fetch next page
246
+ response = client.get(current_url)
247
+
248
+ if not response.ok:
249
+ # Log error but continue with what we have
250
+ import logging
251
+ logger = logging.getLogger("firecrawl")
252
+ logger.warning("Failed to fetch next page", extra={"status_code": response.status_code})
253
+ break
254
+
255
+ page_data = response.json()
256
+
257
+ if not page_data.get("success"):
258
+ break
259
+
260
+ # Add documents from this page
261
+ data_list = page_data.get("data", [])
262
+ for doc_data in data_list:
263
+ if isinstance(doc_data, str):
264
+ continue
265
+ else:
266
+ # Check max_results limit BEFORE adding each document
267
+ if max_results is not None and len(documents) >= max_results:
268
+ break
269
+ documents.append(Document(**normalize_document_input(doc_data)))
270
+
271
+ # Check if we hit max_results limit
272
+ if max_results is not None and len(documents) >= max_results:
273
+ break
274
+
275
+ # Get next URL
276
+ current_url = page_data.get("next")
277
+ page_count += 1
278
+
279
+ return documents
280
+
281
+
193
282
  def cancel_crawl(client: HttpClient, job_id: str) -> bool:
194
283
  """
195
284
  Cancel a running crawl job.
@@ -235,7 +324,7 @@ def wait_for_crawl_completion(
235
324
  Exception: If the job fails
236
325
  TimeoutError: If timeout is reached
237
326
  """
238
- start_time = time.time()
327
+ start_time = time.monotonic()
239
328
 
240
329
  while True:
241
330
  crawl_job = get_crawl_status(client, job_id)
@@ -245,7 +334,7 @@ def wait_for_crawl_completion(
245
334
  return crawl_job
246
335
 
247
336
  # Check timeout
248
- if timeout and (time.time() - start_time) > timeout:
337
+ if timeout is not None and (time.monotonic() - start_time) > timeout:
249
338
  raise TimeoutError(f"Crawl job {job_id} did not complete within {timeout} seconds")
250
339
 
251
340
  # Wait before next poll
firecrawl/v2/types.py CHANGED
@@ -278,7 +278,7 @@ class ScrapeOptions(BaseModel):
278
278
  timeout: Optional[int] = None
279
279
  wait_for: Optional[int] = None
280
280
  mobile: Optional[bool] = None
281
- parsers: Optional[List[str]] = None
281
+ parsers: Optional[Union[List[str], List[Union[str, 'PDFParser']]]] = None
282
282
  actions: Optional[List[Union['WaitAction', 'ScreenshotAction', 'ClickAction', 'WriteAction', 'PressAction', 'ScrollAction', 'ScrapeAction', 'ExecuteJavascriptAction', 'PDFAction']]] = None
283
283
  location: Optional['Location'] = None
284
284
  skip_tls_verification: Optional[bool] = None
@@ -536,6 +536,11 @@ class PDFAction(BaseModel):
536
536
  landscape: Optional[bool] = None
537
537
  scale: Optional[float] = None
538
538
 
539
+ class PDFParser(BaseModel):
540
+ """PDF parser configuration with optional page limit."""
541
+ type: Literal["pdf"] = "pdf"
542
+ max_pages: Optional[int] = None
543
+
539
544
  # Location types
540
545
  class Location(BaseModel):
541
546
  """Location configuration for scraping."""
@@ -594,6 +599,26 @@ class SearchRequest(BaseModel):
594
599
 
595
600
  return normalized_categories
596
601
 
602
+ @field_validator('parsers')
603
+ @classmethod
604
+ def validate_parsers(cls, v):
605
+ """Validate and normalize parsers input."""
606
+ if v is None:
607
+ return v
608
+
609
+ normalized_parsers = []
610
+ for parser in v:
611
+ if isinstance(parser, str):
612
+ normalized_parsers.append(parser)
613
+ elif isinstance(parser, dict):
614
+ normalized_parsers.append(PDFParser(**parser))
615
+ elif isinstance(parser, PDFParser):
616
+ normalized_parsers.append(parser)
617
+ else:
618
+ raise ValueError(f"Invalid parser format: {parser}")
619
+
620
+ return normalized_parsers
621
+
597
622
  class LinkResult(BaseModel):
598
623
  """A generic link result with optional metadata (used by search and map)."""
599
624
  url: str
@@ -671,6 +696,13 @@ class ClientConfig(BaseModel):
671
696
  max_retries: int = 3
672
697
  backoff_factor: float = 0.5
673
698
 
699
+ class PaginationConfig(BaseModel):
700
+ """Configuration for pagination behavior."""
701
+ auto_paginate: bool = True
702
+ max_pages: Optional[int] = Field(default=None, ge=0)
703
+ max_results: Optional[int] = Field(default=None, ge=0)
704
+ max_wait_time: Optional[int] = Field(default=None, ge=0) # seconds
705
+
674
706
  # Response union types
675
707
  AnyResponse = Union[
676
708
  ScrapeResponse,
@@ -679,4 +711,4 @@ AnyResponse = Union[
679
711
  MapResponse,
680
712
  SearchResponse,
681
713
  ErrorResponse,
682
- ]
714
+ ]
@@ -4,6 +4,7 @@ HTTP client utilities for v2 API.
4
4
 
5
5
  import time
6
6
  from typing import Dict, Any, Optional
7
+ from urllib.parse import urlparse, urlunparse, urljoin
7
8
  import requests
8
9
  from .get_version import get_version
9
10
 
@@ -15,6 +16,28 @@ class HttpClient:
15
16
  def __init__(self, api_key: str, api_url: str):
16
17
  self.api_key = api_key
17
18
  self.api_url = api_url
19
+
20
+ def _build_url(self, endpoint: str) -> str:
21
+ base = urlparse(self.api_url)
22
+ ep = urlparse(endpoint)
23
+
24
+ # Absolute or protocol-relative (has netloc)
25
+ if ep.netloc:
26
+ # Different host: keep path/query but force base host/scheme (no token leakage)
27
+ path = ep.path or "/"
28
+ if (ep.hostname or "") != (base.hostname or ""):
29
+ return urlunparse((base.scheme or "https", base.netloc, path, "", ep.query, ""))
30
+ # Same host: normalize scheme to base
31
+ return urlunparse((base.scheme or "https", base.netloc, path, "", ep.query, ""))
32
+
33
+ # Relative (including leading slash or not)
34
+ base_str = self.api_url if self.api_url.endswith("/") else f"{self.api_url}/"
35
+ # Guard protocol-relative like //host/path slipping through as “relative”
36
+ if endpoint.startswith("//"):
37
+ ep2 = urlparse(f"https:{endpoint}")
38
+ path = ep2.path or "/"
39
+ return urlunparse((base.scheme or "https", base.netloc, path, "", ep2.query, ""))
40
+ return urljoin(base_str, endpoint)
18
41
 
19
42
  def _prepare_headers(self, idempotency_key: Optional[str] = None) -> Dict[str, str]:
20
43
  """Prepare headers for API requests."""
@@ -43,7 +66,7 @@ class HttpClient:
43
66
 
44
67
  data['origin'] = f'python-sdk@{version}'
45
68
 
46
- url = f"{self.api_url}{endpoint}"
69
+ url = self._build_url(endpoint)
47
70
 
48
71
  last_exception = None
49
72
 
@@ -84,7 +107,7 @@ class HttpClient:
84
107
  if headers is None:
85
108
  headers = self._prepare_headers()
86
109
 
87
- url = f"{self.api_url}{endpoint}"
110
+ url = self._build_url(endpoint)
88
111
 
89
112
  last_exception = None
90
113
 
@@ -124,7 +147,7 @@ class HttpClient:
124
147
  if headers is None:
125
148
  headers = self._prepare_headers()
126
149
 
127
- url = f"{self.api_url}{endpoint}"
150
+ url = self._build_url(endpoint)
128
151
 
129
152
  last_exception = None
130
153
 
@@ -311,6 +311,20 @@ def prepare_scrape_options(options: Optional[ScrapeOptions]) -> Optional[Dict[st
311
311
  converted_action[action_key] = action_value
312
312
  converted_actions.append(converted_action)
313
313
  scrape_data["actions"] = converted_actions
314
+ elif key == "parsers":
315
+ converted_parsers = []
316
+ for parser in value:
317
+ if isinstance(parser, str):
318
+ converted_parsers.append(parser)
319
+ elif isinstance(parser, dict):
320
+ converted_parsers.append(parser)
321
+ else:
322
+ parser_data = parser.model_dump(exclude_none=True)
323
+ # Convert snake_case to camelCase for API
324
+ if "max_pages" in parser_data:
325
+ parser_data["maxPages"] = parser_data.pop("max_pages")
326
+ converted_parsers.append(parser_data)
327
+ scrape_data["parsers"] = converted_parsers
314
328
  elif key == "location":
315
329
  # Handle location conversion
316
330
  if isinstance(value, dict):
@@ -321,4 +335,4 @@ def prepare_scrape_options(options: Optional[ScrapeOptions]) -> Optional[Dict[st
321
335
  # For fields that don't need conversion, use as-is
322
336
  scrape_data[key] = value
323
337
 
324
- return scrape_data
338
+ return scrape_data
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: firecrawl
3
- Version: 3.4.0
3
+ Version: 4.1.0
4
4
  Summary: Python SDK for Firecrawl API
5
5
  Home-page: https://github.com/firecrawl/firecrawl
6
6
  Author: Mendable.ai
@@ -1,11 +1,11 @@
1
- firecrawl/__init__.py,sha256=Czdc4XJmhkDKcbSuq1wZsiWfezM8v9AUz0K_DEfOH-A,2192
1
+ firecrawl/__init__.py,sha256=BT5Sx5lBRhzEEFNivjIhZaBA8kAeZCPFixbO9n8Myxw,2192
2
2
  firecrawl/client.py,sha256=tp3mUo_3aGPuZ53kpU4bhM-5EtwD_IUWrJ7wm0GMuCc,11159
3
3
  firecrawl/firecrawl.backup.py,sha256=v1FEN3jR4g5Aupg4xp6SLkuFvYMQuUKND2YELbYjE6c,200430
4
4
  firecrawl/types.py,sha256=W9N2pqQuevEIIjYHN9rbDf31E-nwdCECqIn11Foz2T8,2836
5
5
  firecrawl/__tests__/e2e/v2/conftest.py,sha256=I28TUpN5j0-9gM79NlbrDS8Jlsheao657od2f-2xK0Y,2587
6
6
  firecrawl/__tests__/e2e/v2/test_async.py,sha256=ZXpf1FVOJgNclITglrxIyFwP4cOiqzWLicGaxIm70BQ,2526
7
7
  firecrawl/__tests__/e2e/v2/test_batch_scrape.py,sha256=H9GtuwHIFdOQ958SOVThi_kvDDxcXAK_ECRh95ogonQ,3265
8
- firecrawl/__tests__/e2e/v2/test_crawl.py,sha256=cOssZvIwtghAtLiM1QdNLhPEwAxZ9j9umTrBUPtJjpU,9951
8
+ firecrawl/__tests__/e2e/v2/test_crawl.py,sha256=fPbUZIKVtjENo8Mh4HyrwCVuqtNfn3Q1JrETufI27z4,9947
9
9
  firecrawl/__tests__/e2e/v2/test_extract.py,sha256=HgvGiDlyWtFygiPo5EP44Dem1oWrwgRF-hfc1LfeVSU,1670
10
10
  firecrawl/__tests__/e2e/v2/test_map.py,sha256=9sT-Yq8V_8c9esl_bv5hnTA9WXb2Dg81kj6M-s0484c,1618
11
11
  firecrawl/__tests__/e2e/v2/test_scrape.py,sha256=oyroF_WaEdxgD8t_SHkLBBfDRv1_6xZ_7vSTQpwlmA8,7198
@@ -25,6 +25,7 @@ firecrawl/__tests__/unit/v2/methods/test_crawl_params.py,sha256=p9hzg14uAs1iHKXP
25
25
  firecrawl/__tests__/unit/v2/methods/test_crawl_request_preparation.py,sha256=PEKbooNXfQwPpvcPHXABJnveztgAA-RFBhtlSs8uPro,8780
26
26
  firecrawl/__tests__/unit/v2/methods/test_crawl_validation.py,sha256=kErOmHSD01eMjXiMd4rgsMVGd_aU2G9uVymBjbAFoGw,3918
27
27
  firecrawl/__tests__/unit/v2/methods/test_map_request_preparation.py,sha256=toVcgnMp_cFeYsIUuyKGEWZGp0nAAkzaeFGUbY0zY0o,1868
28
+ firecrawl/__tests__/unit/v2/methods/test_pagination.py,sha256=wNc9UtdauII_jzsjlJh645NBRq4IbQij1NeBwbyTjBU,22463
28
29
  firecrawl/__tests__/unit/v2/methods/test_scrape_request_preparation.py,sha256=wDOslsA5BN4kyezlaT5GeMv_Ifn8f461EaA7i5ujnaQ,3482
29
30
  firecrawl/__tests__/unit/v2/methods/test_search_request_preparation.py,sha256=14lUgFpQsiosgMKjDustBRVE0zXnHujBI76F8BC5PZ4,6072
30
31
  firecrawl/__tests__/unit/v2/methods/test_search_validation.py,sha256=7UGcNHpQzCpZbAPYjthfdPFWmAPcoApY-ED-khtuANs,9498
@@ -43,21 +44,21 @@ firecrawl/__tests__/unit/v2/watcher/test_ws_watcher.py,sha256=87w47n0iOihtu4jTR4
43
44
  firecrawl/v1/__init__.py,sha256=aP1oisPeZVGGZynvENc07JySMOZfv_4zAlxQ0ecMJXA,481
44
45
  firecrawl/v1/client.py,sha256=sydurfEFTsXyowyaGryA1lkPxN_r9Nf6iQpM43OwJyM,201672
45
46
  firecrawl/v2/__init__.py,sha256=Jc6a8tBjYG5OPkjDM5pl-notyys-7DEj7PLEfepv3fc,137
46
- firecrawl/v2/client.py,sha256=_DZFZO1aWvODzznK0g2Svcd2-xxXgWGR0d9vniNlk1w,30621
47
- firecrawl/v2/client_async.py,sha256=zwxHis1bSh0tSF1480ze-4XDQEDJ5yDur1ZqtL94dwc,10127
48
- firecrawl/v2/types.py,sha256=F-RCADQFdpAmF5t8LUabLOgyIV02Ol34yNa9y3S3ZMg,22667
47
+ firecrawl/v2/client.py,sha256=AMAHQ8Uz9bsEIy2vmIDNNUIT0FOivhLyj6lesEr1Rbg,31260
48
+ firecrawl/v2/client_async.py,sha256=XyzojIJlWatBGlAMish22H-XHkkH9zHsD6MGtAdtFg8,10487
49
+ firecrawl/v2/types.py,sha256=YnbEmskB4eyfSIBDQK_Kv6xNjID3McagGEoIUiIIbL8,23840
49
50
  firecrawl/v2/watcher.py,sha256=FOU71tqSKxgeuGycu4ye0SLc2dw7clIcoQjPsi-4Csc,14229
50
51
  firecrawl/v2/watcher_async.py,sha256=AVjW2mgABniolSsauK4u0FW8ya6WzRUdyEg2R-8vGCw,10278
51
- firecrawl/v2/methods/batch.py,sha256=us7zUGl7u9ZDIEk2J3rNqj87bkaNjXU27SMFW_fdcg8,11932
52
- firecrawl/v2/methods/crawl.py,sha256=4ZUmanHNuNtq9wbKMAZ3lenuPcNdOaV0kYXqMI5XJJ8,15485
52
+ firecrawl/v2/methods/batch.py,sha256=jFSIPtvulUrPz3Y3zT1gDNwYEf8Botpfh4GOeYsVYRI,14852
53
+ firecrawl/v2/methods/crawl.py,sha256=DWH1wUUDpE0zPSRALkQj_vF-PdsT0A1NyAGtnfcDaR8,18634
53
54
  firecrawl/v2/methods/extract.py,sha256=-Jr4BtraU3b7hd3JIY73V-S69rUclxyXyUpoQb6DCQk,4274
54
55
  firecrawl/v2/methods/map.py,sha256=4SADb0-lkbdOWDmO6k8_TzK0yRti5xsN40N45nUl9uA,2592
55
56
  firecrawl/v2/methods/scrape.py,sha256=CSHBwC-P91UfrW3zHirjNAs2h899FKcWvd1DY_4fJdo,1921
56
57
  firecrawl/v2/methods/search.py,sha256=6BKiQ1aKJjWBKm9BBtKxFKGD74kCKBeMIp_OgjcDFAw,7673
57
58
  firecrawl/v2/methods/usage.py,sha256=OJlkxwaB-AAtgO3WLr9QiqBRmjdh6GVhroCgleegupQ,1460
58
59
  firecrawl/v2/methods/aio/__init__.py,sha256=RocMJnGwnLIvGu3G8ZvY8INkipC7WHZiu2bE31eSyJs,35
59
- firecrawl/v2/methods/aio/batch.py,sha256=GS_xsd_Uib1fxFITBK1sH88VGzFMrIcqJVQqOvMQ540,3735
60
- firecrawl/v2/methods/aio/crawl.py,sha256=pC6bHVk30Hj1EJdAChxpMOg0Xx_GVqq4tIlvU2e5RQ4,6688
60
+ firecrawl/v2/methods/aio/batch.py,sha256=4Uj05ffpMhQA2J_mkvHYYogdXb0IgbKGbomO43b4m94,6741
61
+ firecrawl/v2/methods/aio/crawl.py,sha256=j2Tb2AcGsT6bCiUbB2yjrfvGZqkinUt0tU-SzWmB7Jw,11551
61
62
  firecrawl/v2/methods/aio/extract.py,sha256=IfNr2ETqt4dR73JFzrEYI4kk5vpKnJOG0BmPEjGEoO4,4217
62
63
  firecrawl/v2/methods/aio/map.py,sha256=EuT-5A0cQr_e5SBfEZ6pnl8u0JUwEEvSwhyT2N-QoKU,2326
63
64
  firecrawl/v2/methods/aio/scrape.py,sha256=ilA9qco8YGwCFpE0PN1XBQUyuHPQwH2QioZ-xsfxhgU,1386
@@ -66,14 +67,14 @@ firecrawl/v2/methods/aio/usage.py,sha256=OtBi6X-aT09MMR2dpm3vBCm9JrJZIJLCQ8jJ3L7
66
67
  firecrawl/v2/utils/__init__.py,sha256=i1GgxySmqEXpWSBQCu3iZBPIJG7fXj0QXCDWGwerWNs,338
67
68
  firecrawl/v2/utils/error_handler.py,sha256=Iuf916dHphDY8ObNNlWy75628DFeJ0Rv8ljRp4LttLE,4199
68
69
  firecrawl/v2/utils/get_version.py,sha256=0CxW_41q2hlzIxEWOivUCaYw3GFiSIH32RPUMcIgwAY,492
69
- firecrawl/v2/utils/http_client.py,sha256=_n8mp4xi6GGihg662Lsv6TSlvw9zykyADwEk0fg8mYA,4873
70
+ firecrawl/v2/utils/http_client.py,sha256=gUrC1CvU5sj03w27Lbq-3-yH38Yi_OXiI01-piwA83w,6027
70
71
  firecrawl/v2/utils/http_client_async.py,sha256=iy89_bk2HS3afSRHZ8016eMCa9Fk-5MFTntcOHfbPgE,1936
71
72
  firecrawl/v2/utils/normalize.py,sha256=nlTU6QRghT1YKZzNZlIQj4STSRuSUGrS9cCErZIcY5w,3636
72
- firecrawl/v2/utils/validation.py,sha256=L8by7z-t6GuMGIYkK7il1BM8d-4_-sAdG9hDMF_LeG4,14518
73
- firecrawl-3.4.0.dist-info/licenses/LICENSE,sha256=nPCunEDwjRGHlmjvsiDUyIWbkqqyj3Ej84ntnh0g0zA,1084
73
+ firecrawl/v2/utils/validation.py,sha256=qWWiWaVcvODmVxf9rxIVy1j_dyuJCvdMMUoYhvWUEIU,15269
74
+ firecrawl-4.1.0.dist-info/licenses/LICENSE,sha256=nPCunEDwjRGHlmjvsiDUyIWbkqqyj3Ej84ntnh0g0zA,1084
74
75
  tests/test_change_tracking.py,sha256=_IJ5ShLcoj2fHDBaw-nE4I4lHdmDB617ocK_XMHhXps,4177
75
76
  tests/test_timeout_conversion.py,sha256=PWlIEMASQNhu4cp1OW_ebklnE9NCiigPnEFCtI5N3w0,3996
76
- firecrawl-3.4.0.dist-info/METADATA,sha256=Gyv7wpufayVyAsLKfllRJihHmw43p5UH_lFDJf6i-mU,7392
77
- firecrawl-3.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
78
- firecrawl-3.4.0.dist-info/top_level.txt,sha256=8T3jOaSN5mtLghO-R3MQ8KO290gIX8hmfxQmglBPdLE,16
79
- firecrawl-3.4.0.dist-info/RECORD,,
77
+ firecrawl-4.1.0.dist-info/METADATA,sha256=IFmGv9ChnRIoKksvP9RebpAcqm8c5-SHZhN8LrF04l4,7392
78
+ firecrawl-4.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
79
+ firecrawl-4.1.0.dist-info/top_level.txt,sha256=8T3jOaSN5mtLghO-R3MQ8KO290gIX8hmfxQmglBPdLE,16
80
+ firecrawl-4.1.0.dist-info/RECORD,,