provider.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. from collections.abc import Generator
  2. from datetime import datetime
  3. from typing import Any, Optional
  4. from core.rag.extractor.watercrawl.client import WaterCrawlAPIClient
  5. class WaterCrawlProvider:
  6. def __init__(self, api_key, base_url: str | None = None):
  7. self.client = WaterCrawlAPIClient(api_key, base_url)
  8. def crawl_url(self, url, options: Optional[dict | Any] = None) -> dict:
  9. options = options or {}
  10. spider_options = {
  11. "max_depth": 1,
  12. "page_limit": 1,
  13. "allowed_domains": [],
  14. "exclude_paths": [],
  15. "include_paths": [],
  16. }
  17. if options.get("crawl_sub_pages", True):
  18. spider_options["page_limit"] = options.get("limit", 1)
  19. spider_options["max_depth"] = options.get("max_depth", 1)
  20. spider_options["include_paths"] = options.get("includes", "").split(",") if options.get("includes") else []
  21. spider_options["exclude_paths"] = options.get("excludes", "").split(",") if options.get("excludes") else []
  22. wait_time = options.get("wait_time", 1000)
  23. page_options = {
  24. "exclude_tags": options.get("exclude_tags", "").split(",") if options.get("exclude_tags") else [],
  25. "include_tags": options.get("include_tags", "").split(",") if options.get("include_tags") else [],
  26. "wait_time": max(1000, wait_time), # minimum wait time is 1 second
  27. "include_html": False,
  28. "only_main_content": options.get("only_main_content", True),
  29. "include_links": False,
  30. "timeout": 15000,
  31. "accept_cookies_selector": "#cookies-accept",
  32. "locale": "en-US",
  33. "actions": [],
  34. }
  35. result = self.client.create_crawl_request(url=url, spider_options=spider_options, page_options=page_options)
  36. return {"status": "active", "job_id": result.get("uuid")}
  37. def get_crawl_status(self, crawl_request_id) -> dict:
  38. response = self.client.get_crawl_request(crawl_request_id)
  39. data = []
  40. if response["status"] in ["new", "running"]:
  41. status = "active"
  42. else:
  43. status = "completed"
  44. data = list(self._get_results(crawl_request_id))
  45. time_str = response.get("duration")
  46. time_consuming: float = 0
  47. if time_str:
  48. time_obj = datetime.strptime(time_str, "%H:%M:%S.%f")
  49. time_consuming = (
  50. time_obj.hour * 3600 + time_obj.minute * 60 + time_obj.second + time_obj.microsecond / 1_000_000
  51. )
  52. return {
  53. "status": status,
  54. "job_id": response.get("uuid"),
  55. "total": response.get("options", {}).get("spider_options", {}).get("page_limit", 1),
  56. "current": response.get("number_of_documents", 0),
  57. "data": data,
  58. "time_consuming": time_consuming,
  59. }
  60. def get_crawl_url_data(self, job_id, url) -> dict | None:
  61. if not job_id:
  62. return self.scrape_url(url)
  63. for result in self._get_results(
  64. job_id,
  65. {
  66. # filter by url
  67. "url": url
  68. },
  69. ):
  70. return result
  71. return None
  72. def scrape_url(self, url: str) -> dict:
  73. response = self.client.scrape_url(url=url, sync=True, prefetched=True)
  74. return self._structure_data(response)
  75. def _structure_data(self, result_object: dict) -> dict:
  76. if isinstance(result_object.get("result", {}), str):
  77. raise ValueError("Invalid result object. Expected a dictionary.")
  78. metadata = result_object.get("result", {}).get("metadata", {})
  79. return {
  80. "title": metadata.get("og:title") or metadata.get("title"),
  81. "description": metadata.get("description"),
  82. "source_url": result_object.get("url"),
  83. "markdown": result_object.get("result", {}).get("markdown"),
  84. }
  85. def _get_results(self, crawl_request_id: str, query_params: dict | None = None) -> Generator[dict, None, None]:
  86. page = 0
  87. page_size = 100
  88. query_params = query_params or {}
  89. query_params.update({"prefetched": "true"})
  90. while True:
  91. page += 1
  92. response = self.client.get_crawl_request_results(crawl_request_id, page, page_size, query_params)
  93. if not response["results"]:
  94. break
  95. for result in response["results"]:
  96. yield self._structure_data(result)
  97. if response["next"] is None:
  98. break