scrapegraphai 1.2.1__tar.gz → 1.2.2__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.
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/CHANGELOG.md +7 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/PKG-INFO +2 -2
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/docs/source/getting_started/installation.rst +2 -4
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/manual deployment/deploy_on_pip.sh +0 -3
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/pyproject.toml +3 -4
- scrapegraphai-1.2.2/scrapegraphai/builders/__init__.py +5 -0
- scrapegraphai-1.2.2/scrapegraphai/builders/graph_builder.py +168 -0
- scrapegraphai-1.2.2/scrapegraphai/docloaders/__init__.py +3 -0
- scrapegraphai-1.2.2/scrapegraphai/docloaders/chromium.py +126 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/graphs/__init__.py +1 -0
- scrapegraphai-1.2.2/scrapegraphai/graphs/deep_scraper_graph.py +116 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/graphs/omni_search_graph.py +2 -2
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/graphs/search_graph.py +2 -2
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/graphs/smart_scraper_graph.py +1 -1
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/nodes/__init__.py +1 -2
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/nodes/fetch_node.py +0 -1
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/nodes/generate_answer_node.py +1 -1
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/nodes/merge_answers_node.py +2 -2
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/nodes/robots_node.py +4 -8
- scrapegraphai-1.2.1/examples/custom_graph_domtree.py +0 -171
- scrapegraphai-1.2.1/examples/domtree_example.py +0 -99
- scrapegraphai-1.2.1/examples/faiss_vector.py +0 -34
- scrapegraphai-1.2.1/html_structure +0 -256
- scrapegraphai-1.2.1/html_structure.png +0 -0
- scrapegraphai-1.2.1/manual deployment/rye_update.sh +0 -7
- scrapegraphai-1.2.1/scrapegraphai/nodes/blocks_identifier.py +0 -57
- scrapegraphai-1.2.1/scrapegraphai/nodes/search_node_with_context.py +0 -114
- scrapegraphai-1.2.1/scrapegraphai/utils/aaa.py +0 -212
- scrapegraphai-1.2.1/scrapegraphai/utils/asdt.py +0 -156
- scrapegraphai-1.2.1/scrapegraphai/utils/tree_base.py +0 -59
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/.gitattributes +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/.github/ISSUE_TEMPLATE/custom.md +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/.github/workflows/codeql.yml +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/.github/workflows/dependency-review.yml +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/.github/workflows/pylint.yml +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/.github/workflows/python-publish.yml +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/.github/workflows/release.yml +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/.gitignore +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/.releaserc.yml +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/CODE_OF_CONDUCT.md +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/CONTRIBUTING.md +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/Dockerfile +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/LICENSE +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/README.md +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/SECURITY.md +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/citation.cff +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/docker-compose.yml +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/docs/Makefile +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/docs/README.md +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/docs/assets/apikey_1.png +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/docs/assets/apikey_2.png +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/docs/assets/apikey_3.png +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/docs/assets/apikey_4.png +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/docs/assets/codespaces-badge.png +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/docs/assets/logo_authors.png +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/docs/assets/omniscrapergraph.png +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/docs/assets/omnisearchgraph.png +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/docs/assets/project_overview_diagram.fig +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/docs/assets/project_overview_diagram.png +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/docs/assets/scrapegraphai_logo.png +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/docs/assets/searchgraph.png +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/docs/assets/serp_api_logo.png +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/docs/assets/smartscrapergraph.png +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/docs/assets/speechgraph.png +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/docs/make.bat +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/docs/source/conf.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/docs/source/getting_started/examples.rst +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/docs/source/index.rst +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/docs/source/introduction/contributing.rst +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/docs/source/introduction/overview.rst +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/docs/source/modules/modules.rst +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/docs/source/modules/scrapegraphai.graphs.rst +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/docs/source/modules/scrapegraphai.nodes.rst +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/docs/source/modules/scrapegraphai.rst +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/docs/source/scrapers/benchmarks.rst +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/docs/source/scrapers/graph_config.rst +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/docs/source/scrapers/graphs.rst +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/docs/source/scrapers/llm.rst +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/anthropic/smart_scraper_haiku.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/azure/inputs/books.xml +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/azure/inputs/example.json +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/azure/inputs/username.csv +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/azure/json_scraper_azure.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/azure/search_graph_azure.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/azure/smart_scraper_azure_openai.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/azure/xml_scraper_azure.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/bedrock/smart_scraper_bedrock.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/benchmarks/GenerateScraper/.env.example +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/benchmarks/GenerateScraper/Readme.md +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/benchmarks/GenerateScraper/benchmark_docker.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/benchmarks/GenerateScraper/benchmark_groq.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/benchmarks/GenerateScraper/benchmark_llama3.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/benchmarks/GenerateScraper/benchmark_mistral.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/benchmarks/GenerateScraper/benchmark_openai_gpt35.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/benchmarks/GenerateScraper/benchmark_openai_gpt4.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/benchmarks/GenerateScraper/inputs/example_1.txt +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/benchmarks/GenerateScraper/inputs/example_2.txt +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/benchmarks/SmartScraper/.env.example +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/benchmarks/SmartScraper/Readme.md +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/benchmarks/SmartScraper/benchmark_docker.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/benchmarks/SmartScraper/benchmark_groq.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/benchmarks/SmartScraper/benchmark_llama3.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/benchmarks/SmartScraper/benchmark_mistral.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/benchmarks/SmartScraper/benchmark_openai_gpt35.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/benchmarks/SmartScraper/benchmark_openai_gpt4.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/benchmarks/SmartScraper/inputs/example_1.txt +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/benchmarks/SmartScraper/inputs/example_2.txt +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/benchmarks/readme.md +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/deepseek/.env.example +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/deepseek/csv_scraper_deepseek.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/deepseek/inputs/books.xml +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/deepseek/inputs/example.json +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/deepseek/inputs/username.csv +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/deepseek/json_scraper_deepseek.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/deepseek/script_generator_deepseek.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/deepseek/search_graph_deepseek.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/deepseek/smart_scarper_deepseek.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/deepseek/xml_scraper_deepseek.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/gemini/.env.example +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/gemini/csv_scraper_gemini.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/gemini/custom_graph_gemini.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/gemini/inputs/books.xml +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/gemini/inputs/example.json +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/gemini/inputs/plain_html_example.txt +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/gemini/inputs/username.csv +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/gemini/json_scraper_gemini.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/gemini/readme.md +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/gemini/scrape_plain_text_gemini.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/gemini/scrape_xml_gemini.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/gemini/script_generator_gemini.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/gemini/search_graph_gemini.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/gemini/smart_scraper_gemini.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/gemini/xml_scraper_openai.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/groq/.env.example +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/groq/search_graph_groq_openai.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/groq/smart_scraper_groq_ollama.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/groq/smart_scraper_groq_openai.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/huggingfacehub/smart_scraper_huggingfacehub.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/local_models/csv_scraper_ollama.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/local_models/inputs/books.xml +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/local_models/inputs/example.json +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/local_models/inputs/plain_html_example.txt +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/local_models/inputs/username.csv +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/local_models/json_scraper_ollama.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/local_models/scrape_plain_text_ollama.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/local_models/scrape_xml_ollama.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/local_models/script_generator_ollama.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/local_models/search_graph_ollama.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/local_models/smart_scraper_ollama.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/local_models/xml_scraper_ollama.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/mixed_models/.env.example +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/mixed_models/inputs/books.xml +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/mixed_models/inputs/example.json +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/mixed_models/inputs/plain_html_example.txt +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/mixed_models/readme.md +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/mixed_models/search_graph_groq_ollama.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/mixed_models/smart_scraper_mixed.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/openai/.env.example +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/openai/csv_scraper_openai.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/openai/custom_graph_openai.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/openai/deep_scraper_openai.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/openai/inputs/books.xml +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/openai/inputs/example.json +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/openai/inputs/plain_html_example.txt +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/openai/inputs/username.csv +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/openai/json_scraper_openai.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/openai/omni_scraper_openai.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/openai/omni_search_graph_openai.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/openai/readme.md +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/openai/scrape_plain_text_openai.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/openai/script_generator_openai.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/openai/search_graph_openai.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/openai/smart_scraper_openai.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/openai/speech_graph_openai.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/openai/xml_scraper_openai.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/readme.md +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/single_node/fetch_node.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/single_node/image2text_node.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/examples/single_node/robot_node.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/manual deployment/commit_and_push.sh +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/manual deployment/commit_and_push_with_tests.sh +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/readthedocs.yml +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/requirements-dev.txt +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/requirements.txt +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/__init__.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/graphs/abstract_graph.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/graphs/base_graph.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/graphs/csv_scraper_graph.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/graphs/json_scraper_graph.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/graphs/omni_scraper_graph.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/graphs/pdf_scraper_graph.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/graphs/script_creator_graph.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/graphs/speech_graph.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/graphs/xml_scraper_graph.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/helpers/__init__.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/helpers/models_tokens.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/helpers/nodes_metadata.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/helpers/robots.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/helpers/schemas.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/models/__init__.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/models/anthropic.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/models/azure_openai.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/models/bedrock.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/models/deepseek.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/models/gemini.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/models/groq.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/models/hugging_face.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/models/ollama.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/models/openai.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/models/openai_itt.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/models/openai_tts.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/nodes/base_node.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/nodes/conditional_node.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/nodes/generate_answer_csv_node.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/nodes/generate_answer_omni_node.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/nodes/generate_answer_pdf_node.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/nodes/generate_scraper_node.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/nodes/get_probable_tags_node.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/nodes/graph_iterator_node.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/nodes/image_to_text_node.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/nodes/parse_node.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/nodes/rag_node.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/nodes/search_internet_node.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/nodes/search_link_node.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/nodes/text_to_speech_node.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/utils/__init__.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/utils/cleanup_html.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/utils/convert_to_csv.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/utils/convert_to_json.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/utils/parse_state_keys.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/utils/prettify_exec_info.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/utils/proxy_rotation.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/utils/research_web.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/utils/save_audio_from_bytes.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/utils/sys_dynamic_import.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/scrapegraphai/utils/token_calculator.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/tests/Readme.md +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/tests/graphs/inputs/books.xml +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/tests/graphs/inputs/example.json +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/tests/graphs/inputs/plain_html_example.txt +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/tests/graphs/inputs/username.csv +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/tests/graphs/scrape_json_ollama.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/tests/graphs/scrape_plain_text_llama3_test.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/tests/graphs/scrape_plain_text_mistral_test.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/tests/graphs/scrape_xml_ollama_test.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/tests/graphs/script_generator_test.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/tests/graphs/smart_scraper_ollama_test.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/tests/nodes/fetch_node_test.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/tests/nodes/robot_node_test.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/tests/utils/test_proxy_rotation.py +0 -0
- {scrapegraphai-1.2.1 → scrapegraphai-1.2.2}/tests/utils/test_sys_dynamic_import.py +0 -0
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## [1.2.2](https://github.com/VinciGit00/Scrapegraph-ai/compare/v1.2.1...v1.2.2) (2024-05-15)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* come back to the old version ([cc5adef](https://github.com/VinciGit00/Scrapegraph-ai/commit/cc5adefd29eb2d0d7127515c4a4a72eabbc7eaa8))
|
|
7
|
+
|
|
1
8
|
## [1.2.1](https://github.com/VinciGit00/Scrapegraph-ai/compare/v1.2.0...v1.2.1) (2024-05-15)
|
|
2
9
|
|
|
3
10
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: scrapegraphai
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.2
|
|
4
4
|
Summary: A web scraping library based on LangChain which uses LLM and direct graph logic to create scraping pipelines.
|
|
5
5
|
Author-email: Marco Vinciguerra <mvincig11@gmail.com>, Marco Perini <perinim.98@gmail.com>, Lorenzo Padoan <lorenzo.padoan977@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -10,7 +10,7 @@ Classifier: Intended Audience :: Developers
|
|
|
10
10
|
Classifier: Operating System :: OS Independent
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
13
|
-
Requires-Python:
|
|
13
|
+
Requires-Python: >=3.9
|
|
14
14
|
Requires-Dist: beautifulsoup4==4.12.3
|
|
15
15
|
Requires-Dist: faiss-cpu==1.8.0
|
|
16
16
|
Requires-Dist: free-proxy==1.1.1
|
|
@@ -25,13 +25,11 @@ The library is available on PyPI, so it can be installed using the following com
|
|
|
25
25
|
|
|
26
26
|
It is higly recommended to install the library in a virtual environment (conda, venv, etc.)
|
|
27
27
|
|
|
28
|
-
If
|
|
28
|
+
If your clone the repository, you can install the library using `poetry <https://python-poetry.org/docs/>`_:
|
|
29
29
|
|
|
30
30
|
.. code-block:: bash
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
rye sync
|
|
34
|
-
rye build
|
|
32
|
+
poetry install
|
|
35
33
|
|
|
36
34
|
Additionally on Windows when using WSL
|
|
37
35
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "scrapegraphai"
|
|
3
3
|
|
|
4
|
-
version = "1.2.
|
|
4
|
+
version = "1.2.2"
|
|
5
5
|
|
|
6
6
|
description = "A web scraping library based on LangChain which uses LLM and direct graph logic to create scraping pipelines."
|
|
7
7
|
authors = [
|
|
@@ -10,6 +10,7 @@ authors = [
|
|
|
10
10
|
{ name = "Lorenzo Padoan", email = "lorenzo.padoan977@gmail.com" }
|
|
11
11
|
]
|
|
12
12
|
dependencies = [
|
|
13
|
+
# python = ">=3.9, <3.12"
|
|
13
14
|
"langchain==0.1.15",
|
|
14
15
|
"langchain-openai==0.1.6",
|
|
15
16
|
"langchain-google-genai==1.0.3",
|
|
@@ -61,14 +62,12 @@ classifiers = [
|
|
|
61
62
|
"Programming Language :: Python :: 3",
|
|
62
63
|
"Operating System :: OS Independent",
|
|
63
64
|
]
|
|
64
|
-
requires-python = ">= 3.9
|
|
65
|
-
|
|
65
|
+
requires-python = ">= 3.9"
|
|
66
66
|
|
|
67
67
|
[build-system]
|
|
68
68
|
requires = ["hatchling"]
|
|
69
69
|
build-backend = "hatchling.build"
|
|
70
70
|
|
|
71
|
-
|
|
72
71
|
[tool.rye]
|
|
73
72
|
managed = true
|
|
74
73
|
dev-dependencies = [
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""
|
|
2
|
+
GraphBuilder Module
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from langchain_core.prompts import ChatPromptTemplate
|
|
6
|
+
from langchain.chains import create_extraction_chain
|
|
7
|
+
from ..models import OpenAI, Gemini
|
|
8
|
+
from ..helpers import nodes_metadata, graph_schema
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class GraphBuilder:
|
|
12
|
+
"""
|
|
13
|
+
GraphBuilder is a dynamic tool for constructing web scraping graphs based on user prompts.
|
|
14
|
+
It utilizes a natural language understanding model to interpret user prompts and
|
|
15
|
+
automatically generates a graph configuration for scraping web content.
|
|
16
|
+
|
|
17
|
+
Attributes:
|
|
18
|
+
prompt (str): The user's natural language prompt for the scraping task.
|
|
19
|
+
llm (ChatOpenAI): An instance of the ChatOpenAI class configured
|
|
20
|
+
with the specified llm_config.
|
|
21
|
+
nodes_description (str): A string description of all available nodes and their arguments.
|
|
22
|
+
chain (LLMChain): The extraction chain responsible for
|
|
23
|
+
processing the prompt and creating the graph.
|
|
24
|
+
|
|
25
|
+
Methods:
|
|
26
|
+
build_graph(): Executes the graph creation process based on the user prompt
|
|
27
|
+
and returns the graph configuration.
|
|
28
|
+
convert_json_to_graphviz(json_data): Converts a JSON graph configuration
|
|
29
|
+
to a Graphviz object for visualization.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
prompt (str): The user's natural language prompt describing the desired scraping operation.
|
|
33
|
+
url (str): The target URL from which data is to be scraped.
|
|
34
|
+
llm_config (dict): Configuration parameters for the
|
|
35
|
+
language model, where 'api_key' is mandatory,
|
|
36
|
+
and 'model_name', 'temperature', and 'streaming' can be optionally included.
|
|
37
|
+
|
|
38
|
+
Raises:
|
|
39
|
+
ValueError: If 'api_key' is not included in llm_config.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(self, user_prompt: str, config: dict):
|
|
43
|
+
"""
|
|
44
|
+
Initializes the GraphBuilder with a user prompt and language model configuration.
|
|
45
|
+
"""
|
|
46
|
+
self.user_prompt = user_prompt
|
|
47
|
+
self.config = config
|
|
48
|
+
self.llm = self._create_llm(config["llm"])
|
|
49
|
+
self.nodes_description = self._generate_nodes_description()
|
|
50
|
+
self.chain = self._create_extraction_chain()
|
|
51
|
+
|
|
52
|
+
def _create_llm(self, llm_config: dict):
|
|
53
|
+
"""
|
|
54
|
+
Creates an instance of the OpenAI class with the provided language model configuration.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
OpenAI: An instance of the OpenAI class.
|
|
58
|
+
|
|
59
|
+
Raises:
|
|
60
|
+
ValueError: If 'api_key' is not provided in llm_config.
|
|
61
|
+
"""
|
|
62
|
+
llm_defaults = {
|
|
63
|
+
"temperature": 0,
|
|
64
|
+
"streaming": True
|
|
65
|
+
}
|
|
66
|
+
# Update defaults with any LLM parameters that were provided
|
|
67
|
+
llm_params = {**llm_defaults, **llm_config}
|
|
68
|
+
if "api_key" not in llm_params:
|
|
69
|
+
raise ValueError("LLM configuration must include an 'api_key'.")
|
|
70
|
+
|
|
71
|
+
# select the model based on the model name
|
|
72
|
+
if "gpt-" in llm_params["model"]:
|
|
73
|
+
return OpenAI(llm_params)
|
|
74
|
+
elif "gemini" in llm_params["model"]:
|
|
75
|
+
return Gemini(llm_params)
|
|
76
|
+
raise ValueError("Model not supported")
|
|
77
|
+
|
|
78
|
+
def _generate_nodes_description(self):
|
|
79
|
+
"""
|
|
80
|
+
Generates a string description of all available nodes and their arguments.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
str: A string description of all available nodes and their arguments.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
return "\n".join([
|
|
87
|
+
f"""- {node}: {data["description"]} (Type: {data["type"]},
|
|
88
|
+
Args: {", ".join(data["args"].keys())})"""
|
|
89
|
+
for node, data in nodes_metadata.items()
|
|
90
|
+
])
|
|
91
|
+
|
|
92
|
+
def _create_extraction_chain(self):
|
|
93
|
+
"""
|
|
94
|
+
Creates an extraction chain for processing the user prompt and
|
|
95
|
+
generating the graph configuration.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
LLMChain: An instance of the LLMChain class.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
create_graph_prompt_template = """
|
|
102
|
+
You are an AI that designs direct graphs for web scraping tasks.
|
|
103
|
+
Your goal is to create a web scraping pipeline that is efficient and tailored to the user's requirements.
|
|
104
|
+
You have access to a set of default nodes, each with specific capabilities:
|
|
105
|
+
|
|
106
|
+
{nodes_description}
|
|
107
|
+
|
|
108
|
+
Based on the user's input: "{input}", identify the essential nodes required for the task and suggest a graph configuration that outlines the flow between the chosen nodes.
|
|
109
|
+
""".format(nodes_description=self.nodes_description, input="{input}")
|
|
110
|
+
extraction_prompt = ChatPromptTemplate.from_template(
|
|
111
|
+
create_graph_prompt_template)
|
|
112
|
+
return create_extraction_chain(prompt=extraction_prompt, schema=graph_schema, llm=self.llm)
|
|
113
|
+
|
|
114
|
+
def build_graph(self):
|
|
115
|
+
"""
|
|
116
|
+
Executes the graph creation process based on the user prompt and
|
|
117
|
+
returns the graph configuration.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
dict: A JSON representation of the graph configuration.
|
|
121
|
+
"""
|
|
122
|
+
return self.chain.invoke(self.user_prompt)
|
|
123
|
+
|
|
124
|
+
@staticmethod
|
|
125
|
+
def convert_json_to_graphviz(json_data, format: str = 'pdf'):
|
|
126
|
+
"""
|
|
127
|
+
Converts a JSON graph configuration to a Graphviz object for visualization.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
json_data (dict): A JSON representation of the graph configuration.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
graphviz.Digraph: A Graphviz object representing the graph configuration.
|
|
134
|
+
"""
|
|
135
|
+
try:
|
|
136
|
+
import graphviz
|
|
137
|
+
except ImportError:
|
|
138
|
+
raise ImportError("The 'graphviz' library is required for this functionality. "
|
|
139
|
+
"Please install it from 'https://graphviz.org/download/'.")
|
|
140
|
+
|
|
141
|
+
graph = graphviz.Digraph(comment='ScrapeGraphAI Generated Graph', format=format,
|
|
142
|
+
node_attr={'color': 'lightblue2', 'style': 'filled'})
|
|
143
|
+
|
|
144
|
+
graph_config = json_data["text"][0]
|
|
145
|
+
|
|
146
|
+
# Retrieve nodes, edges, and the entry point from the JSON data
|
|
147
|
+
nodes = graph_config.get('nodes', [])
|
|
148
|
+
edges = graph_config.get('edges', [])
|
|
149
|
+
entry_point = graph_config.get('entry_point')
|
|
150
|
+
|
|
151
|
+
# Add nodes to the graph
|
|
152
|
+
for node in nodes:
|
|
153
|
+
# If this node is the entry point, use a double circle to denote it
|
|
154
|
+
if node['node_name'] == entry_point:
|
|
155
|
+
graph.node(node['node_name'], shape='doublecircle')
|
|
156
|
+
else:
|
|
157
|
+
graph.node(node['node_name'])
|
|
158
|
+
|
|
159
|
+
# Add edges to the graph
|
|
160
|
+
for edge in edges:
|
|
161
|
+
# An edge could potentially have multiple 'to' nodes if it's from a conditional node
|
|
162
|
+
if isinstance(edge['to'], list):
|
|
163
|
+
for to_node in edge['to']:
|
|
164
|
+
graph.edge(edge['from'], to_node)
|
|
165
|
+
else:
|
|
166
|
+
graph.edge(edge['from'], edge['to'])
|
|
167
|
+
|
|
168
|
+
return graph
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Any, AsyncIterator, Iterator, List, Optional
|
|
4
|
+
|
|
5
|
+
from langchain_community.document_loaders.base import BaseLoader
|
|
6
|
+
from langchain_core.documents import Document
|
|
7
|
+
|
|
8
|
+
from ..utils import Proxy, dynamic_import, parse_or_search_proxy
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ChromiumLoader(BaseLoader):
|
|
15
|
+
"""scrapes HTML pages from URLs using a (headless) instance of the
|
|
16
|
+
Chromium web driver with proxy protection
|
|
17
|
+
|
|
18
|
+
Attributes:
|
|
19
|
+
backend: The web driver backend library; defaults to 'playwright'.
|
|
20
|
+
browser_config: A dictionary containing additional browser kwargs.
|
|
21
|
+
headless: whether to run browser in headless mode.
|
|
22
|
+
proxy: A dictionary containing proxy settings; None disables protection.
|
|
23
|
+
urls: A list of URLs to scrape content from.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
urls: List[str],
|
|
29
|
+
*,
|
|
30
|
+
backend: str = "playwright",
|
|
31
|
+
headless: bool = True,
|
|
32
|
+
proxy: Optional[Proxy] = None,
|
|
33
|
+
**kwargs: Any,
|
|
34
|
+
):
|
|
35
|
+
"""Initialize the loader with a list of URL paths.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
backend: The web driver backend library; defaults to 'playwright'.
|
|
39
|
+
headless: whether to run browser in headless mode.
|
|
40
|
+
proxy: A dictionary containing proxy information; None disables protection.
|
|
41
|
+
urls: A list of URLs to scrape content from.
|
|
42
|
+
kwargs: A dictionary containing additional browser kwargs.
|
|
43
|
+
|
|
44
|
+
Raises:
|
|
45
|
+
ImportError: If the required backend package is not installed.
|
|
46
|
+
"""
|
|
47
|
+
message = (
|
|
48
|
+
f"{backend} is required for ChromiumLoader. "
|
|
49
|
+
f"Please install it with `pip install {backend}`."
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
dynamic_import(backend, message)
|
|
53
|
+
|
|
54
|
+
self.backend = backend
|
|
55
|
+
self.browser_config = kwargs
|
|
56
|
+
self.headless = headless
|
|
57
|
+
self.proxy = parse_or_search_proxy(proxy) if proxy else None
|
|
58
|
+
self.urls = urls
|
|
59
|
+
|
|
60
|
+
async def ascrape_playwright(self, url: str) -> str:
|
|
61
|
+
"""
|
|
62
|
+
Asynchronously scrape the content of a given URL using Playwright's async API.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
url (str): The URL to scrape.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
str: The scraped HTML content or an error message if an exception occurs.
|
|
69
|
+
|
|
70
|
+
"""
|
|
71
|
+
from playwright.async_api import async_playwright
|
|
72
|
+
|
|
73
|
+
logger.info("Starting scraping...")
|
|
74
|
+
results = ""
|
|
75
|
+
async with async_playwright() as p:
|
|
76
|
+
browser = await p.chromium.launch(
|
|
77
|
+
headless=self.headless, proxy=self.proxy, **self.browser_config
|
|
78
|
+
)
|
|
79
|
+
try:
|
|
80
|
+
page = await browser.new_page()
|
|
81
|
+
await page.goto(url)
|
|
82
|
+
results = await page.content() # Simply get the HTML content
|
|
83
|
+
logger.info("Content scraped")
|
|
84
|
+
except Exception as e:
|
|
85
|
+
results = f"Error: {e}"
|
|
86
|
+
await browser.close()
|
|
87
|
+
return results
|
|
88
|
+
|
|
89
|
+
def lazy_load(self) -> Iterator[Document]:
|
|
90
|
+
"""
|
|
91
|
+
Lazily load text content from the provided URLs.
|
|
92
|
+
|
|
93
|
+
This method yields Documents one at a time as they're scraped,
|
|
94
|
+
instead of waiting to scrape all URLs before returning.
|
|
95
|
+
|
|
96
|
+
Yields:
|
|
97
|
+
Document: The scraped content encapsulated within a Document object.
|
|
98
|
+
|
|
99
|
+
"""
|
|
100
|
+
scraping_fn = getattr(self, f"ascrape_{self.backend}")
|
|
101
|
+
|
|
102
|
+
for url in self.urls:
|
|
103
|
+
html_content = asyncio.run(scraping_fn(url))
|
|
104
|
+
metadata = {"source": url}
|
|
105
|
+
yield Document(page_content=html_content, metadata=metadata)
|
|
106
|
+
|
|
107
|
+
async def alazy_load(self) -> AsyncIterator[Document]:
|
|
108
|
+
"""
|
|
109
|
+
Asynchronously load text content from the provided URLs.
|
|
110
|
+
|
|
111
|
+
This method leverages asyncio to initiate the scraping of all provided URLs
|
|
112
|
+
simultaneously. It improves performance by utilizing concurrent asynchronous
|
|
113
|
+
requests. Each Document is yielded as soon as its content is available,
|
|
114
|
+
encapsulating the scraped content.
|
|
115
|
+
|
|
116
|
+
Yields:
|
|
117
|
+
Document: A Document object containing the scraped content, along with its
|
|
118
|
+
source URL as metadata.
|
|
119
|
+
"""
|
|
120
|
+
scraping_fn = getattr(self, f"ascrape_{self.backend}")
|
|
121
|
+
|
|
122
|
+
tasks = [scraping_fn(url) for url in self.urls]
|
|
123
|
+
results = await asyncio.gather(*tasks)
|
|
124
|
+
for url, content in zip(self.urls, results):
|
|
125
|
+
metadata = {"source": url}
|
|
126
|
+
yield Document(page_content=content, metadata=metadata)
|
|
@@ -5,6 +5,7 @@ __init__.py file for graphs folder
|
|
|
5
5
|
from .abstract_graph import AbstractGraph
|
|
6
6
|
from .base_graph import BaseGraph
|
|
7
7
|
from .smart_scraper_graph import SmartScraperGraph
|
|
8
|
+
from .deep_scraper_graph import DeepScraperGraph
|
|
8
9
|
from .speech_graph import SpeechGraph
|
|
9
10
|
from .search_graph import SearchGraph
|
|
10
11
|
from .script_creator_graph import ScriptCreatorGraph
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DeepScraperGraph Module
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .base_graph import BaseGraph
|
|
6
|
+
from ..nodes import (
|
|
7
|
+
FetchNode,
|
|
8
|
+
SearchLinkNode,
|
|
9
|
+
ParseNode,
|
|
10
|
+
RAGNode,
|
|
11
|
+
GenerateAnswerNode
|
|
12
|
+
)
|
|
13
|
+
from .abstract_graph import AbstractGraph
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DeepScraperGraph(AbstractGraph):
|
|
17
|
+
"""
|
|
18
|
+
[WIP]
|
|
19
|
+
|
|
20
|
+
DeepScraper is a scraping pipeline that automates the process of
|
|
21
|
+
extracting information from web pages
|
|
22
|
+
using a natural language model to interpret and answer prompts.
|
|
23
|
+
|
|
24
|
+
Unlike SmartScraper, DeepScraper can navigate to the links within the input webpage,
|
|
25
|
+
to fuflfil the task within the prompt.
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
Attributes:
|
|
29
|
+
prompt (str): The prompt for the graph.
|
|
30
|
+
source (str): The source of the graph.
|
|
31
|
+
config (dict): Configuration parameters for the graph.
|
|
32
|
+
llm_model: An instance of a language model client, configured for generating answers.
|
|
33
|
+
embedder_model: An instance of an embedding model client,
|
|
34
|
+
configured for generating embeddings.
|
|
35
|
+
verbose (bool): A flag indicating whether to show print statements during execution.
|
|
36
|
+
headless (bool): A flag indicating whether to run the graph in headless mode.
|
|
37
|
+
Args:
|
|
38
|
+
prompt (str): The prompt for the graph.
|
|
39
|
+
source (str): The source of the graph.
|
|
40
|
+
config (dict): Configuration parameters for the graph.
|
|
41
|
+
Example:
|
|
42
|
+
>>> deep_scraper = DeepScraperGraph(
|
|
43
|
+
... "List me all the job titles and detailed job description.",
|
|
44
|
+
... "https://www.google.com/about/careers/applications/jobs/results/?location=Bangalore%20India",
|
|
45
|
+
... {"llm": {"model": "gpt-3.5-turbo"}}
|
|
46
|
+
... )
|
|
47
|
+
>>> result = deep_scraper.run()
|
|
48
|
+
)
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def __init__(self, prompt: str, source: str, config: dict):
|
|
52
|
+
super().__init__(prompt, config, source)
|
|
53
|
+
|
|
54
|
+
self.input_key = "url" if source.startswith("http") else "local_dir"
|
|
55
|
+
|
|
56
|
+
def _create_graph(self) -> BaseGraph:
|
|
57
|
+
"""
|
|
58
|
+
Creates the graph of nodes representing the workflow for web scraping.
|
|
59
|
+
Returns:
|
|
60
|
+
BaseGraph: A graph instance representing the web scraping workflow.
|
|
61
|
+
"""
|
|
62
|
+
fetch_node = FetchNode(
|
|
63
|
+
input="url | local_dir",
|
|
64
|
+
output=["doc", "link_urls", "img_urls"]
|
|
65
|
+
)
|
|
66
|
+
parse_node = ParseNode(
|
|
67
|
+
input="doc",
|
|
68
|
+
output=["parsed_doc"],
|
|
69
|
+
node_config={
|
|
70
|
+
"chunk_size": self.model_token
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
rag_node = RAGNode(
|
|
74
|
+
input="user_prompt & (parsed_doc | doc)",
|
|
75
|
+
output=["relevant_chunks"],
|
|
76
|
+
node_config={
|
|
77
|
+
"llm_model": self.llm_model,
|
|
78
|
+
"embedder_model": self.embedder_model
|
|
79
|
+
}
|
|
80
|
+
)
|
|
81
|
+
search_node = SearchLinkNode(
|
|
82
|
+
input="user_prompt & relevant_chunks",
|
|
83
|
+
output=["relevant_links"],
|
|
84
|
+
node_config={
|
|
85
|
+
"llm_model": self.llm_model,
|
|
86
|
+
"embedder_model": self.embedder_model
|
|
87
|
+
}
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
return BaseGraph(
|
|
91
|
+
nodes=[
|
|
92
|
+
fetch_node,
|
|
93
|
+
parse_node,
|
|
94
|
+
rag_node,
|
|
95
|
+
search_node
|
|
96
|
+
],
|
|
97
|
+
edges=[
|
|
98
|
+
(fetch_node, parse_node),
|
|
99
|
+
(parse_node, rag_node),
|
|
100
|
+
(rag_node, search_node)
|
|
101
|
+
|
|
102
|
+
],
|
|
103
|
+
entry_point=fetch_node
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
def run(self) -> str:
|
|
107
|
+
"""
|
|
108
|
+
Executes the scraping process and returns the answer to the prompt.
|
|
109
|
+
Returns:
|
|
110
|
+
str: The answer to the prompt.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
inputs = {"user_prompt": self.prompt, self.input_key: self.source}
|
|
114
|
+
self.final_state, self.execution_info = self.graph.execute(inputs)
|
|
115
|
+
|
|
116
|
+
return self.final_state.get("answer", "No answer found.")
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
OmniSearchGraph Module
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from copy import
|
|
5
|
+
from copy import deepcopy
|
|
6
6
|
|
|
7
7
|
from .base_graph import BaseGraph
|
|
8
8
|
from ..nodes import (
|
|
@@ -43,7 +43,7 @@ class OmniSearchGraph(AbstractGraph):
|
|
|
43
43
|
def __init__(self, prompt: str, config: dict):
|
|
44
44
|
|
|
45
45
|
self.max_results = config.get("max_results", 3)
|
|
46
|
-
self.copy_config =
|
|
46
|
+
self.copy_config = deepcopy(config)
|
|
47
47
|
|
|
48
48
|
super().__init__(prompt, config)
|
|
49
49
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
SearchGraph Module
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from copy import
|
|
5
|
+
from copy import deepcopy
|
|
6
6
|
|
|
7
7
|
from .base_graph import BaseGraph
|
|
8
8
|
from ..nodes import (
|
|
@@ -42,7 +42,7 @@ class SearchGraph(AbstractGraph):
|
|
|
42
42
|
def __init__(self, prompt: str, config: dict):
|
|
43
43
|
|
|
44
44
|
self.max_results = config.get("max_results", 3)
|
|
45
|
-
self.copy_config =
|
|
45
|
+
self.copy_config = deepcopy(config)
|
|
46
46
|
|
|
47
47
|
super().__init__(prompt, config)
|
|
48
48
|
|
|
@@ -111,4 +111,4 @@ class SmartScraperGraph(AbstractGraph):
|
|
|
111
111
|
inputs = {"user_prompt": self.prompt, self.input_key: self.source}
|
|
112
112
|
self.final_state, self.execution_info = self.graph.execute(inputs)
|
|
113
113
|
|
|
114
|
-
return self.final_state.get("answer", "No answer found.")
|
|
114
|
+
return self.final_state.get("answer", "No answer found.")
|
|
@@ -19,5 +19,4 @@ from .generate_answer_csv_node import GenerateAnswerCSVNode
|
|
|
19
19
|
from .generate_answer_pdf_node import GenerateAnswerPDFNode
|
|
20
20
|
from .graph_iterator_node import GraphIteratorNode
|
|
21
21
|
from .merge_answers_node import MergeAnswersNode
|
|
22
|
-
from .generate_answer_omni_node import GenerateAnswerOmniNode
|
|
23
|
-
from .search_node_with_context import SearchLinksWithContext
|
|
22
|
+
from .generate_answer_omni_node import GenerateAnswerOmniNode
|
|
@@ -38,7 +38,7 @@ class GenerateAnswerNode(BaseNode):
|
|
|
38
38
|
super().__init__(node_name, "node", input, output, 2, node_config)
|
|
39
39
|
|
|
40
40
|
self.llm_model = node_config["llm_model"]
|
|
41
|
-
self.verbose =
|
|
41
|
+
self.verbose = False if node_config is None else node_config.get(
|
|
42
42
|
"verbose", False)
|
|
43
43
|
|
|
44
44
|
def execute(self, state: dict) -> dict:
|
|
@@ -4,6 +4,7 @@ MergeAnswersNode Module
|
|
|
4
4
|
|
|
5
5
|
# Imports from standard library
|
|
6
6
|
from typing import List, Optional
|
|
7
|
+
from tqdm import tqdm
|
|
7
8
|
|
|
8
9
|
# Imports from Langchain
|
|
9
10
|
from langchain.prompts import PromptTemplate
|
|
@@ -38,8 +39,7 @@ class MergeAnswersNode(BaseNode):
|
|
|
38
39
|
|
|
39
40
|
def execute(self, state: dict) -> dict:
|
|
40
41
|
"""
|
|
41
|
-
Executes the node's logic to merge the answers from multiple graph instances into a
|
|
42
|
-
single answer.
|
|
42
|
+
Executes the node's logic to merge the answers from multiple graph instances into a single answer.
|
|
43
43
|
|
|
44
44
|
Args:
|
|
45
45
|
state (dict): The current state of the graph. The input keys will be used
|
|
@@ -35,15 +35,12 @@ class RobotsNode(BaseNode):
|
|
|
35
35
|
"""
|
|
36
36
|
|
|
37
37
|
def __init__(self, input: str, output: List[str], node_config: Optional[dict]=None,
|
|
38
|
-
|
|
39
38
|
node_name: str = "Robots"):
|
|
40
39
|
super().__init__(node_name, "node", input, output, 1)
|
|
41
40
|
|
|
42
41
|
self.llm_model = node_config["llm_model"]
|
|
43
|
-
|
|
44
|
-
self.
|
|
45
|
-
self.verbose = True if node_config is None else node_config.get(
|
|
46
|
-
"verbose", False)
|
|
42
|
+
self.force_scraping = False if node_config is None else node_config.get("force_scraping", False)
|
|
43
|
+
self.verbose = False if node_config is None else node_config.get("verbose", False)
|
|
47
44
|
|
|
48
45
|
def execute(self, state: dict) -> dict:
|
|
49
46
|
"""
|
|
@@ -100,8 +97,7 @@ class RobotsNode(BaseNode):
|
|
|
100
97
|
loader = AsyncChromiumLoader(f"{base_url}/robots.txt")
|
|
101
98
|
document = loader.load()
|
|
102
99
|
if "ollama" in self.llm_model.model_name:
|
|
103
|
-
self.llm_model.model_name = self.llm_model.model_name.split(
|
|
104
|
-
"/")[-1]
|
|
100
|
+
self.llm_model.model_name = self.llm_model.model_name.split("/")[-1]
|
|
105
101
|
model = self.llm_model.model_name.split("/")[-1]
|
|
106
102
|
|
|
107
103
|
else:
|
|
@@ -126,7 +122,7 @@ class RobotsNode(BaseNode):
|
|
|
126
122
|
if "no" in is_scrapable:
|
|
127
123
|
if self.verbose:
|
|
128
124
|
print("\033[31m(Scraping this website is not allowed)\033[0m")
|
|
129
|
-
|
|
125
|
+
|
|
130
126
|
if not self.force_scraping:
|
|
131
127
|
raise ValueError(
|
|
132
128
|
'The website you selected is not scrapable')
|