From 2f4fd45700ebf1db0c429b5a6249386d1a111615 Mon Sep 17 00:00:00 2001 From: Federico Minutoli Date: Fri, 10 May 2024 21:05:48 +0200 Subject: [PATCH 1/9] fix(pytest): add dependency for mocking testing functions --- poetry.lock | 209 ++++++++++++++++++++++++++----------------------- pyproject.toml | 24 +++++- 2 files changed, 134 insertions(+), 99 deletions(-) diff --git a/poetry.lock b/poetry.lock index 4bdc5dac..edfcab18 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "aiohttp" @@ -245,17 +245,17 @@ lxml = ["lxml"] [[package]] name = "boto3" -version = "1.34.101" +version = "1.34.102" description = "The AWS SDK for Python" optional = false python-versions = ">=3.8" files = [ - {file = "boto3-1.34.101-py3-none-any.whl", hash = "sha256:79b93f3370ea96ce838042bc2eac0c996aee204b01e7e6452eb77abcbe697d6a"}, - {file = "boto3-1.34.101.tar.gz", hash = "sha256:1d854b5880e185db546b4c759fcb664bf3326275064d2b44229cc217e8be9d7e"}, + {file = "boto3-1.34.102-py3-none-any.whl", hash = "sha256:1c1fb2884f85c0ec6b62e6e7ed5a2a6635e1188f3ab5d2b700f7db1cf8464484"}, + {file = "boto3-1.34.102.tar.gz", hash = "sha256:65e4b9fb9ceefe19976e8822ac0cd68d28946d4697e538741d2bbdb5b45ae42f"}, ] [package.dependencies] -botocore = ">=1.34.101,<1.35.0" +botocore = ">=1.34.102,<1.35.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.10.0,<0.11.0" @@ -264,13 +264,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.34.101" +version = "1.34.102" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.8" files = [ - {file = "botocore-1.34.101-py3-none-any.whl", hash = "sha256:f145e8b4b8fc9968f5eb695bdc2fcc8e675df7fbc3c56102dc1f5471be6baf35"}, - {file = "botocore-1.34.101.tar.gz", hash = "sha256:01f3802d25558dd7945d83884bf6885e2f84e1ff27f90b5f09614966fe18c18f"}, + {file = "botocore-1.34.102-py3-none-any.whl", hash = "sha256:79ac7fc2729294395c70eff9c23510f00785ad2acd78d6130cb4379e9f27da86"}, + {file = "botocore-1.34.102.tar.gz", hash = "sha256:e2f8a9f4bac6f7b568e6e981ac2a2500bc992329c85dde8546f0cae8605dd009"}, ] [package.dependencies] @@ -418,13 +418,13 @@ files = [ [[package]] name = "dataclasses-json" -version = "0.6.5" +version = "0.6.6" description = "Easily serialize dataclasses to and from JSON." optional = false python-versions = "<4.0,>=3.7" files = [ - {file = "dataclasses_json-0.6.5-py3-none-any.whl", hash = "sha256:f49c77aa3a85cac5bf5b7f65f4790ca0d2be8ef4d92c75e91ba0103072788a39"}, - {file = "dataclasses_json-0.6.5.tar.gz", hash = "sha256:1c287594d9fcea72dc42d6d3836cf14848c2dc5ce88f65ed61b36b57f515fe26"}, + {file = "dataclasses_json-0.6.6-py3-none-any.whl", hash = "sha256:e54c5c87497741ad454070ba0ed411523d46beb5da102e221efb873801b0ba85"}, + {file = "dataclasses_json-0.6.6.tar.gz", hash = "sha256:0c09827d26fffda27f1be2fed7a7a01a29c5ddcd2eb6393ad5ebf9d77e9deae8"}, ] [package.dependencies] @@ -1501,6 +1501,7 @@ files = [ {file = "lxml-5.2.1-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c38d7b9a690b090de999835f0443d8aa93ce5f2064035dfc48f27f02b4afc3d0"}, {file = "lxml-5.2.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5670fb70a828663cc37552a2a85bf2ac38475572b0e9b91283dc09efb52c41d1"}, {file = "lxml-5.2.1-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:958244ad566c3ffc385f47dddde4145088a0ab893504b54b52c041987a8c1863"}, + {file = "lxml-5.2.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b6241d4eee5f89453307c2f2bfa03b50362052ca0af1efecf9fef9a41a22bb4f"}, {file = "lxml-5.2.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:2a66bf12fbd4666dd023b6f51223aed3d9f3b40fef06ce404cb75bafd3d89536"}, {file = "lxml-5.2.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:9123716666e25b7b71c4e1789ec829ed18663152008b58544d95b008ed9e21e9"}, {file = "lxml-5.2.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:0c3f67e2aeda739d1cc0b1102c9a9129f7dc83901226cc24dd72ba275ced4218"}, @@ -1525,7 +1526,6 @@ files = [ {file = "lxml-5.2.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:9e2addd2d1866fe112bc6f80117bcc6bc25191c5ed1bfbcf9f1386a884252ae8"}, {file = "lxml-5.2.1-cp37-cp37m-win32.whl", hash = "sha256:f51969bac61441fd31f028d7b3b45962f3ecebf691a510495e5d2cd8c8092dbd"}, {file = "lxml-5.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:b0b58fbfa1bf7367dde8a557994e3b1637294be6cf2169810375caf8571a085c"}, - {file = "lxml-5.2.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3e183c6e3298a2ed5af9d7a356ea823bccaab4ec2349dc9ed83999fd289d14d5"}, {file = "lxml-5.2.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:804f74efe22b6a227306dd890eecc4f8c59ff25ca35f1f14e7482bbce96ef10b"}, {file = "lxml-5.2.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:08802f0c56ed150cc6885ae0788a321b73505d2263ee56dad84d200cab11c07a"}, {file = "lxml-5.2.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f8c09ed18ecb4ebf23e02b8e7a22a05d6411911e6fabef3a36e4f371f4f2585"}, @@ -1877,13 +1877,13 @@ files = [ [[package]] name = "openai" -version = "1.27.0" +version = "1.28.0" description = "The official Python library for the openai API" optional = false python-versions = ">=3.7.1" files = [ - {file = "openai-1.27.0-py3-none-any.whl", hash = "sha256:1183346fae6e63cb3a9134e397c0067690dc9d94ceb36eb0eb2c1bb9a1542aca"}, - {file = "openai-1.27.0.tar.gz", hash = "sha256:498adc80ba81a95324afdfd11a71fa43a37e1d94a5ca5f4542e52fe9568d995b"}, + {file = "openai-1.28.0-py3-none-any.whl", hash = "sha256:94b5a99f5121e1747dda1bb8fff31820d5ab4b49056a9cf2e3605f5c90011955"}, + {file = "openai-1.28.0.tar.gz", hash = "sha256:ac43b8b48aec70de4b76cfc96ae906bf8d5814427475b9dabb662f84f655f0e1"}, ] [package.dependencies] @@ -2310,6 +2310,23 @@ tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +[[package]] +name = "pytest-mock" +version = "3.14.0" +description = "Thin-wrapper around the mock package for easier use with pytest" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, + {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, +] + +[package.dependencies] +pytest = ">=6.2.5" + +[package.extras] +dev = ["pre-commit", "pytest-asyncio", "tox"] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -2411,90 +2428,90 @@ files = [ [[package]] name = "regex" -version = "2024.4.28" +version = "2024.5.10" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.8" files = [ - {file = "regex-2024.4.28-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd196d056b40af073d95a2879678585f0b74ad35190fac04ca67954c582c6b61"}, - {file = "regex-2024.4.28-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8bb381f777351bd534462f63e1c6afb10a7caa9fa2a421ae22c26e796fe31b1f"}, - {file = "regex-2024.4.28-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:47af45b6153522733aa6e92543938e97a70ce0900649ba626cf5aad290b737b6"}, - {file = "regex-2024.4.28-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99d6a550425cc51c656331af0e2b1651e90eaaa23fb4acde577cf15068e2e20f"}, - {file = "regex-2024.4.28-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bf29304a8011feb58913c382902fde3395957a47645bf848eea695839aa101b7"}, - {file = "regex-2024.4.28-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:92da587eee39a52c91aebea8b850e4e4f095fe5928d415cb7ed656b3460ae79a"}, - {file = "regex-2024.4.28-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6277d426e2f31bdbacb377d17a7475e32b2d7d1f02faaecc48d8e370c6a3ff31"}, - {file = "regex-2024.4.28-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:28e1f28d07220c0f3da0e8fcd5a115bbb53f8b55cecf9bec0c946eb9a059a94c"}, - {file = "regex-2024.4.28-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:aaa179975a64790c1f2701ac562b5eeb733946eeb036b5bcca05c8d928a62f10"}, - {file = "regex-2024.4.28-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6f435946b7bf7a1b438b4e6b149b947c837cb23c704e780c19ba3e6855dbbdd3"}, - {file = "regex-2024.4.28-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:19d6c11bf35a6ad077eb23852827f91c804eeb71ecb85db4ee1386825b9dc4db"}, - {file = "regex-2024.4.28-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:fdae0120cddc839eb8e3c15faa8ad541cc6d906d3eb24d82fb041cfe2807bc1e"}, - {file = "regex-2024.4.28-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e672cf9caaf669053121f1766d659a8813bd547edef6e009205378faf45c67b8"}, - {file = "regex-2024.4.28-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f57515750d07e14743db55d59759893fdb21d2668f39e549a7d6cad5d70f9fea"}, - {file = "regex-2024.4.28-cp310-cp310-win32.whl", hash = "sha256:a1409c4eccb6981c7baabc8888d3550df518add6e06fe74fa1d9312c1838652d"}, - {file = "regex-2024.4.28-cp310-cp310-win_amd64.whl", hash = "sha256:1f687a28640f763f23f8a9801fe9e1b37338bb1ca5d564ddd41619458f1f22d1"}, - {file = "regex-2024.4.28-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:84077821c85f222362b72fdc44f7a3a13587a013a45cf14534df1cbbdc9a6796"}, - {file = "regex-2024.4.28-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b45d4503de8f4f3dc02f1d28a9b039e5504a02cc18906cfe744c11def942e9eb"}, - {file = "regex-2024.4.28-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:457c2cd5a646dd4ed536c92b535d73548fb8e216ebee602aa9f48e068fc393f3"}, - {file = "regex-2024.4.28-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b51739ddfd013c6f657b55a508de8b9ea78b56d22b236052c3a85a675102dc6"}, - {file = "regex-2024.4.28-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:459226445c7d7454981c4c0ce0ad1a72e1e751c3e417f305722bbcee6697e06a"}, - {file = "regex-2024.4.28-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:670fa596984b08a4a769491cbdf22350431970d0112e03d7e4eeaecaafcd0fec"}, - {file = "regex-2024.4.28-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe00f4fe11c8a521b173e6324d862ee7ee3412bf7107570c9b564fe1119b56fb"}, - {file = "regex-2024.4.28-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:36f392dc7763fe7924575475736bddf9ab9f7a66b920932d0ea50c2ded2f5636"}, - {file = "regex-2024.4.28-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:23a412b7b1a7063f81a742463f38821097b6a37ce1e5b89dd8e871d14dbfd86b"}, - {file = "regex-2024.4.28-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f1d6e4b7b2ae3a6a9df53efbf199e4bfcff0959dbdb5fd9ced34d4407348e39a"}, - {file = "regex-2024.4.28-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:499334ad139557de97cbc4347ee921c0e2b5e9c0f009859e74f3f77918339257"}, - {file = "regex-2024.4.28-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:0940038bec2fe9e26b203d636c44d31dd8766abc1fe66262da6484bd82461ccf"}, - {file = "regex-2024.4.28-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:66372c2a01782c5fe8e04bff4a2a0121a9897e19223d9eab30c54c50b2ebeb7f"}, - {file = "regex-2024.4.28-cp311-cp311-win32.whl", hash = "sha256:c77d10ec3c1cf328b2f501ca32583625987ea0f23a0c2a49b37a39ee5c4c4630"}, - {file = "regex-2024.4.28-cp311-cp311-win_amd64.whl", hash = "sha256:fc0916c4295c64d6890a46e02d4482bb5ccf33bf1a824c0eaa9e83b148291f90"}, - {file = "regex-2024.4.28-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:08a1749f04fee2811c7617fdd46d2e46d09106fa8f475c884b65c01326eb15c5"}, - {file = "regex-2024.4.28-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b8eb28995771c087a73338f695a08c9abfdf723d185e57b97f6175c5051ff1ae"}, - {file = "regex-2024.4.28-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dd7ef715ccb8040954d44cfeff17e6b8e9f79c8019daae2fd30a8806ef5435c0"}, - {file = "regex-2024.4.28-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb0315a2b26fde4005a7c401707c5352df274460f2f85b209cf6024271373013"}, - {file = "regex-2024.4.28-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f2fc053228a6bd3a17a9b0a3f15c3ab3cf95727b00557e92e1cfe094b88cc662"}, - {file = "regex-2024.4.28-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fe9739a686dc44733d52d6e4f7b9c77b285e49edf8570754b322bca6b85b4cc"}, - {file = "regex-2024.4.28-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a74fcf77d979364f9b69fcf8200849ca29a374973dc193a7317698aa37d8b01c"}, - {file = "regex-2024.4.28-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:965fd0cf4694d76f6564896b422724ec7b959ef927a7cb187fc6b3f4e4f59833"}, - {file = "regex-2024.4.28-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2fef0b38c34ae675fcbb1b5db760d40c3fc3612cfa186e9e50df5782cac02bcd"}, - {file = "regex-2024.4.28-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bc365ce25f6c7c5ed70e4bc674f9137f52b7dd6a125037f9132a7be52b8a252f"}, - {file = "regex-2024.4.28-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:ac69b394764bb857429b031d29d9604842bc4cbfd964d764b1af1868eeebc4f0"}, - {file = "regex-2024.4.28-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:144a1fc54765f5c5c36d6d4b073299832aa1ec6a746a6452c3ee7b46b3d3b11d"}, - {file = "regex-2024.4.28-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2630ca4e152c221072fd4a56d4622b5ada876f668ecd24d5ab62544ae6793ed6"}, - {file = "regex-2024.4.28-cp312-cp312-win32.whl", hash = "sha256:7f3502f03b4da52bbe8ba962621daa846f38489cae5c4a7b5d738f15f6443d17"}, - {file = "regex-2024.4.28-cp312-cp312-win_amd64.whl", hash = "sha256:0dd3f69098511e71880fb00f5815db9ed0ef62c05775395968299cb400aeab82"}, - {file = "regex-2024.4.28-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:374f690e1dd0dbdcddea4a5c9bdd97632cf656c69113f7cd6a361f2a67221cb6"}, - {file = "regex-2024.4.28-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:25f87ae6b96374db20f180eab083aafe419b194e96e4f282c40191e71980c666"}, - {file = "regex-2024.4.28-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5dbc1bcc7413eebe5f18196e22804a3be1bfdfc7e2afd415e12c068624d48247"}, - {file = "regex-2024.4.28-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f85151ec5a232335f1be022b09fbbe459042ea1951d8a48fef251223fc67eee1"}, - {file = "regex-2024.4.28-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57ba112e5530530fd175ed550373eb263db4ca98b5f00694d73b18b9a02e7185"}, - {file = "regex-2024.4.28-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:224803b74aab56aa7be313f92a8d9911dcade37e5f167db62a738d0c85fdac4b"}, - {file = "regex-2024.4.28-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a54a047b607fd2d2d52a05e6ad294602f1e0dec2291152b745870afc47c1397"}, - {file = "regex-2024.4.28-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a2a512d623f1f2d01d881513af9fc6a7c46e5cfffb7dc50c38ce959f9246c94"}, - {file = "regex-2024.4.28-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c06bf3f38f0707592898428636cbb75d0a846651b053a1cf748763e3063a6925"}, - {file = "regex-2024.4.28-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1031a5e7b048ee371ab3653aad3030ecfad6ee9ecdc85f0242c57751a05b0ac4"}, - {file = "regex-2024.4.28-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d7a353ebfa7154c871a35caca7bfd8f9e18666829a1dc187115b80e35a29393e"}, - {file = "regex-2024.4.28-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:7e76b9cfbf5ced1aca15a0e5b6f229344d9b3123439ffce552b11faab0114a02"}, - {file = "regex-2024.4.28-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5ce479ecc068bc2a74cb98dd8dba99e070d1b2f4a8371a7dfe631f85db70fe6e"}, - {file = "regex-2024.4.28-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7d77b6f63f806578c604dca209280e4c54f0fa9a8128bb8d2cc5fb6f99da4150"}, - {file = "regex-2024.4.28-cp38-cp38-win32.whl", hash = "sha256:d84308f097d7a513359757c69707ad339da799e53b7393819ec2ea36bc4beb58"}, - {file = "regex-2024.4.28-cp38-cp38-win_amd64.whl", hash = "sha256:2cc1b87bba1dd1a898e664a31012725e48af826bf3971e786c53e32e02adae6c"}, - {file = "regex-2024.4.28-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7413167c507a768eafb5424413c5b2f515c606be5bb4ef8c5dee43925aa5718b"}, - {file = "regex-2024.4.28-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:108e2dcf0b53a7c4ab8986842a8edcb8ab2e59919a74ff51c296772e8e74d0ae"}, - {file = "regex-2024.4.28-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f1c5742c31ba7d72f2dedf7968998730664b45e38827637e0f04a2ac7de2f5f1"}, - {file = "regex-2024.4.28-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecc6148228c9ae25ce403eade13a0961de1cb016bdb35c6eafd8e7b87ad028b1"}, - {file = "regex-2024.4.28-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7d893c8cf0e2429b823ef1a1d360a25950ed11f0e2a9df2b5198821832e1947"}, - {file = "regex-2024.4.28-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4290035b169578ffbbfa50d904d26bec16a94526071ebec3dadbebf67a26b25e"}, - {file = "regex-2024.4.28-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44a22ae1cfd82e4ffa2066eb3390777dc79468f866f0625261a93e44cdf6482b"}, - {file = "regex-2024.4.28-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd24fd140b69f0b0bcc9165c397e9b2e89ecbeda83303abf2a072609f60239e2"}, - {file = "regex-2024.4.28-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:39fb166d2196413bead229cd64a2ffd6ec78ebab83fff7d2701103cf9f4dfd26"}, - {file = "regex-2024.4.28-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9301cc6db4d83d2c0719f7fcda37229691745168bf6ae849bea2e85fc769175d"}, - {file = "regex-2024.4.28-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7c3d389e8d76a49923683123730c33e9553063d9041658f23897f0b396b2386f"}, - {file = "regex-2024.4.28-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:99ef6289b62042500d581170d06e17f5353b111a15aa6b25b05b91c6886df8fc"}, - {file = "regex-2024.4.28-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:b91d529b47798c016d4b4c1d06cc826ac40d196da54f0de3c519f5a297c5076a"}, - {file = "regex-2024.4.28-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:43548ad74ea50456e1c68d3c67fff3de64c6edb85bcd511d1136f9b5376fc9d1"}, - {file = "regex-2024.4.28-cp39-cp39-win32.whl", hash = "sha256:05d9b6578a22db7dedb4df81451f360395828b04f4513980b6bd7a1412c679cc"}, - {file = "regex-2024.4.28-cp39-cp39-win_amd64.whl", hash = "sha256:3986217ec830c2109875be740531feb8ddafe0dfa49767cdcd072ed7e8927962"}, - {file = "regex-2024.4.28.tar.gz", hash = "sha256:83ab366777ea45d58f72593adf35d36ca911ea8bd838483c1823b883a121b0e4"}, + {file = "regex-2024.5.10-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:eda3dd46df535da787ffb9036b5140f941ecb91701717df91c9daf64cabef953"}, + {file = "regex-2024.5.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1d5bd666466c8f00a06886ce1397ba8b12371c1f1c6d1bef11013e9e0a1464a8"}, + {file = "regex-2024.5.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:32e5f3b8e32918bfbdd12eca62e49ab3031125c454b507127ad6ecbd86e62fca"}, + {file = "regex-2024.5.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:534efd2653ebc4f26fc0e47234e53bf0cb4715bb61f98c64d2774a278b58c846"}, + {file = "regex-2024.5.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:193b7c6834a06f722f0ce1ba685efe80881de7c3de31415513862f601097648c"}, + {file = "regex-2024.5.10-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:160ba087232c5c6e2a1e7ad08bd3a3f49b58c815be0504d8c8aacfb064491cd8"}, + {file = "regex-2024.5.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:951be1eae7b47660412dc4938777a975ebc41936d64e28081bf2e584b47ec246"}, + {file = "regex-2024.5.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8a0f0ab5453e409586b11ebe91c672040bc804ca98d03a656825f7890cbdf88"}, + {file = "regex-2024.5.10-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9e6d4d6ae1827b2f8c7200aaf7501c37cf3f3896c86a6aaf2566448397c823dd"}, + {file = "regex-2024.5.10-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:161a206c8f3511e2f5fafc9142a2cc25d7fe9a1ec5ad9b4ad2496a7c33e1c5d2"}, + {file = "regex-2024.5.10-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:44b3267cea873684af022822195298501568ed44d542f9a2d9bebc0212e99069"}, + {file = "regex-2024.5.10-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:560278c9975694e1f0bc50da187abf2cdc1e4890739ea33df2bc4a85eeef143e"}, + {file = "regex-2024.5.10-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:70364a097437dd0a90b31cd77f09f7387ad9ac60ef57590971f43b7fca3082a5"}, + {file = "regex-2024.5.10-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:42be5de7cc8c1edac55db92d82b68dc8e683b204d6f5414c5a51997a323d7081"}, + {file = "regex-2024.5.10-cp310-cp310-win32.whl", hash = "sha256:9a8625849387b9d558d528e263ecc9c0fbde86cfa5c2f0eef43fff480ae24d71"}, + {file = "regex-2024.5.10-cp310-cp310-win_amd64.whl", hash = "sha256:903350bf44d7e4116b4d5898b30b15755d61dcd3161e3413a49c7db76f0bee5a"}, + {file = "regex-2024.5.10-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bf9596cba92ce7b1fd32c7b07c6e3212c7eed0edc271757e48bfcd2b54646452"}, + {file = "regex-2024.5.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:45cc13d398b6359a7708986386f72bd156ae781c3e83a68a6d4cee5af04b1ce9"}, + {file = "regex-2024.5.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ad45f3bccfcb00868f2871dce02a755529838d2b86163ab8a246115e80cfb7d6"}, + {file = "regex-2024.5.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33d19f0cde6838c81acffff25c7708e4adc7dd02896c9ec25c3939b1500a1778"}, + {file = "regex-2024.5.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0a9f89d7db5ef6bdf53e5cc8e6199a493d0f1374b3171796b464a74ebe8e508a"}, + {file = "regex-2024.5.10-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c6c71cf92b09e5faa72ea2c68aa1f61c9ce11cb66fdc5069d712f4392ddfd00"}, + {file = "regex-2024.5.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7467ad8b0eac0b28e52679e972b9b234b3de0ea5cee12eb50091d2b68145fe36"}, + {file = "regex-2024.5.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bc0db93ad039fc2fe32ccd3dd0e0e70c4f3d6e37ae83f0a487e1aba939bd2fbd"}, + {file = "regex-2024.5.10-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fa9335674d7c819674467c7b46154196c51efbaf5f5715187fd366814ba3fa39"}, + {file = "regex-2024.5.10-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7dda3091838206969c2b286f9832dff41e2da545b99d1cfaea9ebd8584d02708"}, + {file = "regex-2024.5.10-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:504b5116e2bd1821efd815941edff7535e93372a098e156bb9dffde30264e798"}, + {file = "regex-2024.5.10-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:91b53dea84415e8115506cc62e441a2b54537359c63d856d73cb1abe05af4c9a"}, + {file = "regex-2024.5.10-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1a3903128f9e17a500618e80c68165c78c741ebb17dd1a0b44575f92c3c68b02"}, + {file = "regex-2024.5.10-cp311-cp311-win32.whl", hash = "sha256:236cace6c1903effd647ed46ce6dd5d76d54985fc36dafc5256032886736c85d"}, + {file = "regex-2024.5.10-cp311-cp311-win_amd64.whl", hash = "sha256:12446827f43c7881decf2c126762e11425de5eb93b3b0d8b581344c16db7047a"}, + {file = "regex-2024.5.10-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:14905ed75c7a6edf423eb46c213ed3f4507c38115f1ed3c00f4ec9eafba50e58"}, + {file = "regex-2024.5.10-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4fad420b14ae1970a1f322e8ae84a1d9d89375eb71e1b504060ab2d1bfe68f3c"}, + {file = "regex-2024.5.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c46a76a599fcbf95f98755275c5527304cc4f1bb69919434c1e15544d7052910"}, + {file = "regex-2024.5.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0faecb6d5779753a6066a3c7a0471a8d29fe25d9981ca9e552d6d1b8f8b6a594"}, + {file = "regex-2024.5.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aab65121229c2ecdf4a31b793d99a6a0501225bd39b616e653c87b219ed34a49"}, + {file = "regex-2024.5.10-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:50e7e96a527488334379e05755b210b7da4a60fc5d6481938c1fa053e0c92184"}, + {file = "regex-2024.5.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba034c8db4b264ef1601eb33cd23d87c5013b8fb48b8161debe2e5d3bd9156b0"}, + {file = "regex-2024.5.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:031219782d97550c2098d9a68ce9e9eaefe67d2d81d8ff84c8354f9c009e720c"}, + {file = "regex-2024.5.10-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:62b5f7910b639f3c1d122d408421317c351e213ca39c964ad4121f27916631c6"}, + {file = "regex-2024.5.10-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:cd832bd9b6120d6074f39bdfbb3c80e416848b07ac72910f1c7f03131a6debc3"}, + {file = "regex-2024.5.10-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:e91b1976358e17197157b405cab408a5f4e33310cda211c49fc6da7cffd0b2f0"}, + {file = "regex-2024.5.10-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:571452362d552de508c37191b6abbbb660028b8b418e2d68c20779e0bc8eaaa8"}, + {file = "regex-2024.5.10-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5253dcb0bfda7214523de58b002eb0090cb530d7c55993ce5f6d17faf953ece7"}, + {file = "regex-2024.5.10-cp312-cp312-win32.whl", hash = "sha256:2f30a5ab8902f93930dc6f627c4dd5da2703333287081c85cace0fc6e21c25af"}, + {file = "regex-2024.5.10-cp312-cp312-win_amd64.whl", hash = "sha256:3799e36d60a35162bb35b2246d8bb012192b7437dff807ef79c14e7352706306"}, + {file = "regex-2024.5.10-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:bbdc5db2c98ac2bf1971ffa1410c87ca7a15800415f788971e8ba8520fc0fda9"}, + {file = "regex-2024.5.10-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6ccdeef4584450b6f0bddd5135354908dacad95425fcb629fe36d13e48b60f32"}, + {file = "regex-2024.5.10-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:29d839829209f3c53f004e1de8c3113efce6d98029f044fa5cfee666253ee7e6"}, + {file = "regex-2024.5.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0709ba544cf50bd5cb843df4b8bb6701bae2b70a8e88da9add8386cbca5c1385"}, + {file = "regex-2024.5.10-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:972b49f2fe1047b9249c958ec4fa1bdd2cf8ce305dc19d27546d5a38e57732d8"}, + {file = "regex-2024.5.10-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9cdbb1998da94607d5eec02566b9586f0e70d6438abf1b690261aac0edda7ab6"}, + {file = "regex-2024.5.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf7c8ee4861d9ef5b1120abb75846828c811f932d63311596ad25fa168053e00"}, + {file = "regex-2024.5.10-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d35d4cc9270944e95f9c88af757b0c9fc43f396917e143a5756608462c5223b"}, + {file = "regex-2024.5.10-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8722f72068b3e1156a4b2e1afde6810f1fc67155a9fa30a4b9d5b4bc46f18fb0"}, + {file = "regex-2024.5.10-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:696639a73ca78a380acfaa0a1f6dd8220616a99074c05bba9ba8bb916914b224"}, + {file = "regex-2024.5.10-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ea057306ab469130167014b662643cfaed84651c792948891d003cf0039223a5"}, + {file = "regex-2024.5.10-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:b43b78f9386d3d932a6ce5af4b45f393d2e93693ee18dc4800d30a8909df700e"}, + {file = "regex-2024.5.10-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:c43395a3b7cc9862801a65c6994678484f186ce13c929abab44fb8a9e473a55a"}, + {file = "regex-2024.5.10-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0bc94873ba11e34837bffd7e5006703abeffc4514e2f482022f46ce05bd25e67"}, + {file = "regex-2024.5.10-cp38-cp38-win32.whl", hash = "sha256:1118ba9def608250250f4b3e3f48c62f4562ba16ca58ede491b6e7554bfa09ff"}, + {file = "regex-2024.5.10-cp38-cp38-win_amd64.whl", hash = "sha256:458d68d34fb74b906709735c927c029e62f7d06437a98af1b5b6258025223210"}, + {file = "regex-2024.5.10-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:15e593386ec6331e0ab4ac0795b7593f02ab2f4b30a698beb89fbdc34f92386a"}, + {file = "regex-2024.5.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ca23b41355ba95929e9505ee04e55495726aa2282003ed9b012d86f857d3e49b"}, + {file = "regex-2024.5.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2c8982ee19ccecabbaeac1ba687bfef085a6352a8c64f821ce2f43e6d76a9298"}, + {file = "regex-2024.5.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7117cb7d6ac7f2e985f3d18aa8a1728864097da1a677ffa69e970ca215baebf1"}, + {file = "regex-2024.5.10-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b66421f8878a0c82fc0c272a43e2121c8d4c67cb37429b764f0d5ad70b82993b"}, + {file = "regex-2024.5.10-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:224a9269f133564109ce668213ef3cb32bc72ccf040b0b51c72a50e569e9dc9e"}, + {file = "regex-2024.5.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab98016541543692a37905871a5ffca59b16e08aacc3d7d10a27297b443f572d"}, + {file = "regex-2024.5.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:51d27844763c273a122e08a3e86e7aefa54ee09fb672d96a645ece0454d8425e"}, + {file = "regex-2024.5.10-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:853cc36e756ff673bf984e9044ccc8fad60b95a748915dddeab9488aea974c73"}, + {file = "regex-2024.5.10-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4e7eaf9df15423d07b6050fb91f86c66307171b95ea53e2d87a7993b6d02c7f7"}, + {file = "regex-2024.5.10-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:169fd0acd7a259f58f417e492e93d0e15fc87592cd1e971c8c533ad5703b5830"}, + {file = "regex-2024.5.10-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:334b79ce9c08f26b4659a53f42892793948a613c46f1b583e985fd5a6bf1c149"}, + {file = "regex-2024.5.10-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:f03b1dbd4d9596dd84955bb40f7d885204d6aac0d56a919bb1e0ff2fb7e1735a"}, + {file = "regex-2024.5.10-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cfa6d61a76c77610ba9274c1a90a453062bdf6887858afbe214d18ad41cf6bde"}, + {file = "regex-2024.5.10-cp39-cp39-win32.whl", hash = "sha256:249fbcee0a277c32a3ce36d8e36d50c27c968fdf969e0fbe342658d4e010fbc8"}, + {file = "regex-2024.5.10-cp39-cp39-win_amd64.whl", hash = "sha256:0ce56a923f4c01d7568811bfdffe156268c0a7aae8a94c902b92fe34c4bde785"}, + {file = "regex-2024.5.10.tar.gz", hash = "sha256:304e7e2418146ae4d0ef0e9ffa28f881f7874b45b4994cc2279b21b6e7ae50c8"}, ] [[package]] @@ -3343,4 +3360,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = ">=3.9, <3.12" -content-hash = "4248d989a9691c2ded2ba504a6099527c02ff5ffbbbf4ce900f85c7f5c548518" +content-hash = "07bf578772c7eaa4af39a7ab55b216c1253c36b34b78bd34f2181bb91f6a133b" diff --git a/pyproject.toml b/pyproject.toml index 9cd6f618..78febc6d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,14 +7,32 @@ description = "A web scraping library based on LangChain which uses LLM and dire authors = [ "Marco Vinciguerra ", "Marco Perini ", - "Lorenzo Padoan " + "Lorenzo Padoan ", ] license = "MIT" readme = "README.md" homepage = "https://scrapegraph-ai.readthedocs.io/" repository = "https://github.com/VinciGit00/Scrapegraph-ai" documentation = "https://scrapegraph-doc.onrender.com/" -keywords = ["scrapegraph", "scrapegraphai", "langchain", "ai", "artificial intelligence", "gpt", "machine learning", "rag", "nlp", "natural language processing", "openai", "scraping", "web scraping", "web scraping library", "web scraping tool", "webscraping", "graph"] +keywords = [ + "scrapegraph", + "scrapegraphai", + "langchain", + "ai", + "artificial intelligence", + "gpt", + "machine learning", + "rag", + "nlp", + "natural language processing", + "openai", + "scraping", + "web scraping", + "web scraping library", + "web scraping tool", + "webscraping", + "graph", +] classifiers = [ "Intended Audience :: Developers", "Topic :: Software Development :: Libraries :: Python Modules", @@ -46,6 +64,7 @@ yahoo-search-py = "^0.3" [tool.poetry.dev-dependencies] pytest = "8.0.0" +pytest-mock = "3.14.0" [tool.poetry.group.docs] optional = true @@ -57,4 +76,3 @@ sphinx-rtd-theme = "2.0.0" [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" - From db2234bf5d2f2589b080cd4136f33c4f4443bdfb Mon Sep 17 00:00:00 2001 From: Federico Minutoli Date: Fri, 10 May 2024 21:06:05 +0200 Subject: [PATCH 2/9] feat(webdriver-backend): add dynamic import scripts from module and file --- scrapegraphai/utils/sys_dynamic_import.py | 67 ++++++++++++++++ tests/utils/test_sys_dynamic_import.py | 94 +++++++++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 scrapegraphai/utils/sys_dynamic_import.py create mode 100644 tests/utils/test_sys_dynamic_import.py diff --git a/scrapegraphai/utils/sys_dynamic_import.py b/scrapegraphai/utils/sys_dynamic_import.py new file mode 100644 index 00000000..30f75d15 --- /dev/null +++ b/scrapegraphai/utils/sys_dynamic_import.py @@ -0,0 +1,67 @@ +"""high-level module for dynamic importing of python modules at runtime + +source code inspired by https://gist.github.com/DiTo97/46f4b733396b8d7a8f1d4d22db902cfc +""" + +import sys +import typing + + +if typing.TYPE_CHECKING: + import types + + +def srcfile_import(modpath: str, modname: str) -> "types.ModuleType": + """imports a python module from its srcfile + + Args: + modpath: The srcfile absolute path + modname: The module name in the scope + + Returns: + The imported module + + Raises: + ImportError: If the module cannot be imported from the srcfile + """ + import importlib.util # noqa: F401 + + # + spec = importlib.util.spec_from_file_location(modname, modpath) + + if spec is None: + message = f"missing spec for module at {modpath}" + raise ImportError(message) + + if spec.loader is None: + message = f"missing spec loader for module at {modpath}" + raise ImportError(message) + + module = importlib.util.module_from_spec(spec) + + # adds the module to the global scope + sys.modules[modname] = module + + spec.loader.exec_module(module) + + return module + + +def dynamic_import(modname: str, message: str = "") -> None: + """imports a python module at runtime + + Args: + modname: The module name in the scope + message: The display message in case of error + + Raises: + ImportError: If the module cannot be imported at runtime + """ + if modname not in sys.modules: + try: + import importlib # noqa: F401 + + module = importlib.import_module(modname) + sys.modules[modname] = module + except ImportError as x: + raise ImportError(message) from x diff --git a/tests/utils/test_sys_dynamic_import.py b/tests/utils/test_sys_dynamic_import.py new file mode 100644 index 00000000..5f544de2 --- /dev/null +++ b/tests/utils/test_sys_dynamic_import.py @@ -0,0 +1,94 @@ +import os +import sys + +import pytest + +from scrapegraphai.utils.sys_dynamic_import import dynamic_import, srcfile_import + + +def _create_sample_file(filepath: str, content: str): + """creates a sample file at some path with some content""" + with open(filepath, "w", encoding="utf-8") as f: + f.write(content) + + +def _delete_sample_file(filepath: str): + """deletes a sample file at some path""" + if os.path.exists(filepath): + os.remove(filepath) + + +def test_srcfile_import_success(): + modpath = "example1.py" + modname = "example1" + + _create_sample_file(modpath, "def foo(): return 'bar'") + + module = srcfile_import(modpath, modname) + + assert hasattr(module, "foo") + assert module.foo() == "bar" + assert modname in sys.modules + + _delete_sample_file(modpath) + + +def test_srcfile_import_missing_spec(): + modpath = "nonexistent1.py" + modname = "nonexistent1" + + with pytest.raises(FileNotFoundError): + srcfile_import(modpath, modname) + + +def test_srcfile_import_missing_spec_loader(mocker): + modpath = "example2.py" + modname = "example2" + + _create_sample_file(modpath, "") + + mock_spec = mocker.Mock(loader=None) + + mocker.patch("importlib.util.spec_from_file_location", return_value=mock_spec) + + with pytest.raises(ImportError) as error_info: + srcfile_import(modpath, modname) + + assert "missing spec loader for module at" in str(error_info.value) + + _delete_sample_file(modpath) + + +def test_dynamic_import_success(): + print(sys.modules) + modname = "playwright" + assert modname not in sys.modules + + dynamic_import(modname) + + assert modname in sys.modules + + import playwright # noqa: F401 + + +def test_dynamic_import_module_already_imported(): + modname = "json" + + import json # noqa: F401 + + assert modname in sys.modules + + dynamic_import(modname) + + assert modname in sys.modules + + +def test_dynamic_import_import_error_with_custom_message(): + modname = "nonexistent2" + message = "could not import module" + + with pytest.raises(ImportError) as error_info: + dynamic_import(modname, message=message) + + assert str(error_info.value) == message + assert modname not in sys.modules From 217013181da06abe8d71d9db70e809ea4ebd8236 Mon Sep 17 00:00:00 2001 From: Federico Minutoli Date: Fri, 10 May 2024 21:09:48 +0200 Subject: [PATCH 3/9] feat(proxy-rotation): add parse (IP address) or search (from broker) functionality for proxy rotation the broker has been made fully configurable for anonymity level, admissible locations, scheme and max shape not to waste resources, unlike the original `free-proxy` package. other options have been explored (e.g., `proxybroker`, `proxybroker2`) due to their built-in proxy server and rotation capabilities, but the former is no longer maintained, and the latter has issue with any python version outside of python 3.9 --- scrapegraphai/utils/__init__.py | 6 +- scrapegraphai/utils/proxy_rotation.py | 232 ++++++++++++++++++++++++-- tests/utils/test_proxy_rotation.py | 121 ++++++++++++++ 3 files changed, 341 insertions(+), 18 deletions(-) create mode 100644 tests/utils/test_proxy_rotation.py diff --git a/scrapegraphai/utils/__init__.py b/scrapegraphai/utils/__init__.py index 0aee7839..74c70f84 100644 --- a/scrapegraphai/utils/__init__.py +++ b/scrapegraphai/utils/__init__.py @@ -1,8 +1,10 @@ """ __init__.py file for utils folder """ -from .save_audio_from_bytes import save_audio_from_bytes + from .convert_to_csv import convert_to_csv from .convert_to_json import convert_to_json from .prettify_exec_info import prettify_exec_info -from .proxy_rotation import proxy_generator +from .proxy_rotation import Proxy, parse_or_search_proxy, search_proxy_servers +from .save_audio_from_bytes import save_audio_from_bytes +from .sys_dynamic_import import dynamic_import, srcfile_import diff --git a/scrapegraphai/utils/proxy_rotation.py b/scrapegraphai/utils/proxy_rotation.py index 576a91e4..0ca204e0 100644 --- a/scrapegraphai/utils/proxy_rotation.py +++ b/scrapegraphai/utils/proxy_rotation.py @@ -1,34 +1,234 @@ """ Module for rotating proxies """ + +import ipaddress +import random +from typing import List, Optional, Set, TypedDict + +import requests +from fp.errors import FreeProxyException from fp.fp import FreeProxy -def proxy_generator(num_ips: int) -> list: - """ - Generates a specified number of proxy IP addresses using the FreeProxy library. +class ProxyBrokerCriteria(TypedDict, total=False): + """proxy broker criteria""" + + anonymous: bool + countryset: Set[str] + secure: bool + timeout: float + search_outside_if_empty: bool + + +class ProxySettings(TypedDict, total=False): + """proxy settings""" + + server: str + bypass: str + username: str + password: str + + +class Proxy(ProxySettings): + """proxy server information""" + + criteria: ProxyBrokerCriteria + + +def search_proxy_servers( + anonymous: bool = True, + countryset: Optional[Set[str]] = None, + secure: bool = False, + timeout: float = 5.0, + max_shape: int = 5, + search_outside_if_empty: bool = True, +) -> List[str]: + """search for proxy servers that match the specified broker criteria Args: - num_ips (int): The number of proxy IPs to generate and rotate through. + anonymous: whether proxy servers should have minimum level-1 anonymity. + countryset: admissible proxy servers locations. + secure: whether proxy servers should support HTTP or HTTPS; defaults to HTTP; + timeout: The maximum timeout for proxy responses; defaults to 5.0 seconds. + max_shape: The maximum number of proxy servers to return; defaults to 5. + search_outside_if_empty: whether countryset should be extended if empty. Returns: - list: A list of proxy IP addresses. + A list of proxy server URLs matching the criteria. Example: - >>> proxy_generator(5) + >>> search_proxy_servers( + ... anonymous=True, + ... countryset={"GB", "US"}, + ... secure=True, + ... timeout=1.0 + ... max_shape=2 + ... ) [ - '192.168.1.1:8080', - '103.10.63.135:8080', - '176.9.75.42:8080', - '37.57.216.2:8080', - '113.20.31.250:8080' + "http://103.10.63.135:8080", + "http://113.20.31.250:8080", ] + """ + proxybroker = FreeProxy( + anonym=anonymous, + country_id=countryset, + elite=True, + https=secure, + timeout=timeout, + ) + + def search_all(proxybroker: FreeProxy, k: int, search_outside: bool) -> List[str]: + candidateset = proxybroker.get_proxy_list(search_outside) + random.shuffle(candidateset) + + positive = set() + + for address in candidateset: + setting = {proxybroker.schema: f"http://{address}"} + + try: + server = proxybroker._FreeProxy__check_if_proxy_is_working(setting) - This function fetches fresh proxies and indexes them, making it easy to manage multiple proxy configurations. + if not server: + continue + + positive.add(server) + + if len(positive) < k: + continue + + return list(positive) + + except requests.exceptions.RequestException: + continue + + n = len(positive) + + if n < k and search_outside: + proxybroker.country_id = None + + try: + negative = set(search_all(proxybroker, k - n, False)) + except FreeProxyException: + negative = set() + + positive = positive | negative + + if not positive: + raise FreeProxyException("missing proxy servers for criteria") + + return list(positive) + + return search_all(proxybroker, max_shape, search_outside_if_empty) + + +def _parse_proxy(proxy: ProxySettings) -> ProxySettings: + """parses a proxy configuration with known server + + Args: + proxy: The proxy configuration to parse. + + Returns: + A 'playwright' compliant proxy configuration. """ + assert "server" in proxy, "missing server in the proxy configuration" + + auhtorization = [x in proxy for x in ("username", "password")] + + message = "username and password must be provided in pairs or not at all" + + assert all(auhtorization) or not any(auhtorization), message + + parsed = {"server": proxy["server"]} + + if proxy.get("bypass"): + parsed["bypass"] = proxy["bypass"] + + if all(auhtorization): + parsed["username"] = proxy["username"] + parsed["password"] = proxy["password"] + + return parsed + + +def _search_proxy(proxy: Proxy) -> ProxySettings: + """searches for a proxy server matching the specified broker criteria + + Args: + proxy: The proxy configuration to search for. + + Returns: + A 'playwright' compliant proxy configuration. + """ + server = search_proxy_servers(max_shape=1, **proxy.get("criteria", {}))[0] + + return {"server": server} + + +def is_ipv4_address(address: str) -> bool: + """If a proxy address conforms to a IPv4 address""" + try: + ipaddress.IPv4Address(address) + return True + except ipaddress.AddressValueError: + return False + + +def parse_or_search_proxy(proxy: Proxy) -> ProxySettings: + """parses a proxy configuration or searches for a new one matching + the specified broker criteria + + Args: + proxy: The proxy configuration to parse or search for. + + Returns: + A 'playwright' compliant proxy configuration. + + Notes: + - If the proxy server is a IP address, it is assumed to be + a proxy server address. + - If the proxy server is 'broker', a proxy server is searched for + based on the provided broker criteria. + + Example: + >>> proxy = { + ... "server": "broker", + ... "criteria": { + ... "anonymous": True, + ... "countryset": {"GB", "US"}, + ... "secure": True, + ... "timeout": 5.0 + ... "search_outside_if_empty": False + ... } + ... } + + >>> parse_or_search_proxy(proxy) + { + "server": "", + } + + Example: + >>> proxy = { + ... "server": "192.168.1.1:8080", + ... "username": "", + ... "password": "" + ... } + + >>> parse_or_search_proxy(proxy) + { + "server": "192.168.1.1:8080", + "username": "", + "password": "" + } + """ + assert "server" in proxy, "missing server in the proxy configuration" + + server_address = proxy["server"].split(":", maxsplit=1)[0] + + if is_ipv4_address(server_address): + return _parse_proxy(proxy) - res = [] + assert proxy["server"] == "broker", "unknown proxy server" - for i in range(0, num_ips): - res.append(FreeProxy().get()) - return res + return _search_proxy(proxy) diff --git a/tests/utils/test_proxy_rotation.py b/tests/utils/test_proxy_rotation.py new file mode 100644 index 00000000..8acbdb30 --- /dev/null +++ b/tests/utils/test_proxy_rotation.py @@ -0,0 +1,121 @@ +import pytest +from fp.errors import FreeProxyException + +from scrapegraphai.utils.proxy_rotation import ( + Proxy, + _parse_proxy, + _search_proxy, + is_ipv4_address, + parse_or_search_proxy, + search_proxy_servers, +) + + +def test_search_proxy_servers_success(): + servers = search_proxy_servers( + anonymous=True, + countryset={"US"}, + secure=False, + timeout=10.0, + max_shape=2, + search_outside_if_empty=True, + ) + + assert isinstance(servers, list) + assert all(isinstance(server, str) for server in servers) + + +def test_search_proxy_servers_exception(): + with pytest.raises(FreeProxyException): + search_proxy_servers( + anonymous=True, + countryset={"XX"}, + secure=True, + timeout=1.0, + max_shape=2, + search_outside_if_empty=False, + ) + + +def test_parse_proxy_success(): + proxy = { + "server": "192.168.1.1:8080", + "username": "user", + "password": "pass", + "bypass": "*.local", + } + + parsed_proxy = _parse_proxy(proxy) + assert parsed_proxy == proxy + + +def test_parse_proxy_exception(): + invalid_proxy = {"server": "192.168.1.1:8080", "username": "user"} + + with pytest.raises(AssertionError) as error_info: + _parse_proxy(invalid_proxy) + + assert "username and password must be provided in pairs" in str(error_info.value) + + +def test_search_proxy_success(): + proxy = Proxy(criteria={"anonymous": True, "countryset": {"US"}}) + found_proxy = _search_proxy(proxy) + + assert isinstance(found_proxy, dict) + assert "server" in found_proxy + + +def test_is_ipv4_address(): + assert is_ipv4_address("192.168.1.1") is True + assert is_ipv4_address("999.999.999.999") is False + assert is_ipv4_address("no-address") is False + + +def test_parse_or_search_proxy_success(): + proxy = { + "server": "192.168.1.1:8080", + "username": "username", + "password": "password", + } + + parsed_proxy = parse_or_search_proxy(proxy) + assert parsed_proxy == proxy + + proxy_broker = { + "server": "broker", + "criteria": { + "anonymous": True, + "countryset": {"US"}, + "secure": True, + "timeout": 10.0, + }, + } + + found_proxy = parse_or_search_proxy(proxy_broker) + + assert isinstance(found_proxy, dict) + assert "server" in found_proxy + + +def test_parse_or_search_proxy_exception(): + proxy = { + "username": "username", + "password": "password", + } + + with pytest.raises(AssertionError) as error_info: + parse_or_search_proxy(proxy) + + assert "missing server in the proxy configuration" in str(error_info.value) + + +def test_parse_or_search_proxy_unknown_server(): + proxy = { + "server": "unknown", + } + + with pytest.raises(AssertionError) as error_info: + parse_or_search_proxy(proxy) + + assert "unknown proxy server" in str(error_info.value) From 768719cce80953fa6cbe283e442420116c438f16 Mon Sep 17 00:00:00 2001 From: Federico Minutoli Date: Fri, 10 May 2024 21:13:38 +0200 Subject: [PATCH 4/9] feat(safe-web-driver): enchanced the original `AsyncChromiumLoader` web driver with proxy protection and flexible kwargs and backend the original class prevents passing kwargs down to the playwright backend, making some config unfeasible, including passing a proxy server to the web driver. the new class has backward compatibility with the original, but 1) allows any kwarg to be passed down to the web driver, 2) allows specifying the web driver backend (only playwright is supported for now) in case more (e.g., selenium) will be supported in the future and 3) automatically fetches a suitable proxy if one is not passed already --- scrapegraphai/docloaders/__init__.py | 3 + scrapegraphai/docloaders/chromium.py | 125 +++++++++++++++++++++++++++ scrapegraphai/nodes/fetch_node.py | 92 +++++++++++--------- 3 files changed, 180 insertions(+), 40 deletions(-) create mode 100644 scrapegraphai/docloaders/__init__.py create mode 100644 scrapegraphai/docloaders/chromium.py diff --git a/scrapegraphai/docloaders/__init__.py b/scrapegraphai/docloaders/__init__.py new file mode 100644 index 00000000..a9e45407 --- /dev/null +++ b/scrapegraphai/docloaders/__init__.py @@ -0,0 +1,3 @@ +"""__init__.py file for docloaders folder""" + +from .chromium import ChromiumLoader diff --git a/scrapegraphai/docloaders/chromium.py b/scrapegraphai/docloaders/chromium.py new file mode 100644 index 00000000..0377f803 --- /dev/null +++ b/scrapegraphai/docloaders/chromium.py @@ -0,0 +1,125 @@ +import asyncio +import logging +from typing import Any, AsyncIterator, Iterator, List, Optional + +from langchain_core.documents import Document + +from ..utils import Proxy, dynamic_import, parse_or_search_proxy + + +logger = logging.getLogger(__name__) + + +class ChromiumLoader: + """scrapes HTML pages from URLs using a (headless) instance of the + Chromium web driver with proxy protection + + Attributes: + backend: The web driver backend library; defaults to 'playwright'. + browser_config: A dictionary containing additional browser kwargs. + headless: whether to run browser in headless mode. + proxy: A dictionary containing proxy settings; None disables protection. + urls: A list of URLs to scrape content from. + """ + + def __init__( + self, + urls: List[str], + *, + backend: str = "playwright", + headless: bool = True, + proxy: Optional[Proxy] = None, + **kwargs: Any, + ): + """Initialize the loader with a list of URL paths. + + Args: + backend: The web driver backend library; defaults to 'playwright'. + headless: whether to run browser in headless mode. + proxy: A dictionary containing proxy information; None disables protection. + urls: A list of URLs to scrape content from. + kwargs: A dictionary containing additional browser kwargs. + + Raises: + ImportError: If the required backend package is not installed. + """ + message = ( + f"{backend} is required for ChromiumLoader. " + f"Please install it with `pip install {backend}`." + ) + + dynamic_import(backend, message) + + self.backend = backend + self.browser_config = kwargs + self.headless = headless + self.proxy = parse_or_search_proxy(proxy) if proxy else None + self.urls = urls + + async def ascrape_playwright(self, url: str) -> str: + """ + Asynchronously scrape the content of a given URL using Playwright's async API. + + Args: + url (str): The URL to scrape. + + Returns: + str: The scraped HTML content or an error message if an exception occurs. + + """ + from playwright.async_api import async_playwright + + logger.info("Starting scraping...") + results = "" + async with async_playwright() as p: + browser = await p.chromium.launch( + headless=self.headless, proxy=self.proxy, **self.browser_config + ) + try: + page = await browser.new_page() + await page.goto(url) + results = await page.content() # Simply get the HTML content + logger.info("Content scraped") + except Exception as e: + results = f"Error: {e}" + await browser.close() + return results + + def lazy_load(self) -> Iterator[Document]: + """ + Lazily load text content from the provided URLs. + + This method yields Documents one at a time as they're scraped, + instead of waiting to scrape all URLs before returning. + + Yields: + Document: The scraped content encapsulated within a Document object. + + """ + scraping_fn = getattr(self, f"ascrape_{self.backend}") + + for url in self.urls: + html_content = asyncio.run(scraping_fn(url)) + metadata = {"source": url} + yield Document(page_content=html_content, metadata=metadata) + + async def alazy_load(self) -> AsyncIterator[Document]: + """ + Asynchronously load text content from the provided URLs. + + This method leverages asyncio to initiate the scraping of all provided URLs + simultaneously. It improves performance by utilizing concurrent asynchronous + requests. Each Document is yielded as soon as its content is available, + encapsulating the scraped content. + + Yields: + Document: A Document object containing the scraped content, along with its + source URL as metadata. + """ + scraping_fn = getattr(self, f"ascrape_{self.backend}") + + tasks = [scraping_fn(url) for url in self.urls] + results = await asyncio.gather(*tasks) + for url, content in zip(self.urls, results): + metadata = {"source": url} + yield Document(page_content=content, metadata=metadata) diff --git a/scrapegraphai/nodes/fetch_node.py b/scrapegraphai/nodes/fetch_node.py index 52266b42..51a66518 100644 --- a/scrapegraphai/nodes/fetch_node.py +++ b/scrapegraphai/nodes/fetch_node.py @@ -1,21 +1,24 @@ """ FetchNode Module """ -import pandas as pd + import json from typing import List, Optional -from langchain_community.document_loaders import AsyncChromiumLoader -from langchain_core.documents import Document + +import pandas as pd from langchain_community.document_loaders import PyPDFLoader -from .base_node import BaseNode +from langchain_core.documents import Document + +from ..docloaders import ChromiumLoader from ..utils.remover import remover +from .base_node import BaseNode class FetchNode(BaseNode): """ A node responsible for fetching the HTML content of a specified URL and updating - the graph's state with this content. It uses the AsyncChromiumLoader to fetch the - content asynchronously. + the graph's state with this content. It uses ChromiumLoader to fetch + the content from a web page asynchronously (with proxy protection). This node acts as a starting point in many scraping workflows, preparing the state with the necessary HTML content for further processing by subsequent nodes in the graph. @@ -31,13 +34,21 @@ class FetchNode(BaseNode): node_name (str): The unique identifier name for the node, defaulting to "Fetch". """ - def __init__(self, input: str, output: List[str], node_config: Optional[dict] = None, node_name: str = "Fetch"): + def __init__( + self, + input: str, + output: List[str], + node_config: Optional[dict] = None, + node_name: str = "Fetch", + ): super().__init__(node_name, "node", input, output, 1) - self.headless = True if node_config is None else node_config.get( - "headless", True) - self.verbose = False if node_config is None else node_config.get( - "verbose", False) + self.headless = ( + True if node_config is None else node_config.get("headless", True) + ) + self.verbose = ( + False if node_config is None else node_config.get("verbose", False) + ) def execute(self, state): """ @@ -64,10 +75,14 @@ def execute(self, state): input_data = [state[key] for key in input_keys] source = input_data[0] - if self.input == "json_dir" or self.input == "xml_dir" or self.input == "csv_dir": - compressed_document = [Document(page_content=source, metadata={ - "source": "local_dir" - })] + if ( + self.input == "json_dir" + or self.input == "xml_dir" + or self.input == "csv_dir" + ): + compressed_document = [ + Document(page_content=source, metadata={"source": "local_dir"}) + ] # if it is a local directory # handling for pdf @@ -76,45 +91,42 @@ def execute(self, state): compressed_document = loader.load() elif self.input == "csv": - compressed_document = [Document(page_content=str(pd.read_csv(source)), metadata={ - "source": "csv" - })] + compressed_document = [ + Document( + page_content=str(pd.read_csv(source)), metadata={"source": "csv"} + ) + ] elif self.input == "json": f = open(source) - compressed_document = [Document(page_content=str(json.load(f)), metadata={ - "source": "json" - })] + compressed_document = [ + Document(page_content=str(json.load(f)), metadata={"source": "json"}) + ] elif self.input == "xml": - with open(source, 'r', encoding='utf-8') as f: + with open(source, "r", encoding="utf-8") as f: data = f.read() - compressed_document = [Document(page_content=data, metadata={ - "source": "xml" - })] + compressed_document = [ + Document(page_content=data, metadata={"source": "xml"}) + ] elif self.input == "pdf_dir": pass elif not source.startswith("http"): - compressed_document = [Document(page_content=remover(source), metadata={ - "source": "local_dir" - })] + compressed_document = [ + Document(page_content=remover(source), metadata={"source": "local_dir"}) + ] else: - if self.node_config is not None and self.node_config.get("endpoint") is not None: + loader_kwargs = {} - loader = AsyncChromiumLoader( - [source], - proxies={"http": self.node_config["endpoint"]}, - headless=self.headless, - ) - else: - loader = AsyncChromiumLoader( - [source], - headless=self.headless, - ) + if self.node_config is not None: + loader_kwargs = self.node_config.get("loader_kwargs", {}) + + loader = ChromiumLoader([source], headless=self.headless, **loader_kwargs) document = loader.load() compressed_document = [ - Document(page_content=remover(str(document[0].page_content)))] + Document(page_content=remover(str(document[0].page_content))) + ] state.update({self.output[0]: compressed_document}) return state From b54d984c134c8cbc432fd111bb161d3d53cf4a85 Mon Sep 17 00:00:00 2001 From: Federico Minutoli Date: Sat, 11 May 2024 11:44:56 +0200 Subject: [PATCH 5/9] fix(chromium-loader): ensure it subclasses langchain's base loader --- scrapegraphai/docloaders/chromium.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scrapegraphai/docloaders/chromium.py b/scrapegraphai/docloaders/chromium.py index 0377f803..7d499245 100644 --- a/scrapegraphai/docloaders/chromium.py +++ b/scrapegraphai/docloaders/chromium.py @@ -2,6 +2,7 @@ import logging from typing import Any, AsyncIterator, Iterator, List, Optional +from langchain_community.document_loaders.base import BaseLoader from langchain_core.documents import Document from ..utils import Proxy, dynamic_import, parse_or_search_proxy @@ -10,7 +11,7 @@ logger = logging.getLogger(__name__) -class ChromiumLoader: +class ChromiumLoader(BaseLoader): """scrapes HTML pages from URLs using a (headless) instance of the Chromium web driver with proxy protection From 1e9a56461632999c5dc09f5aa930c14c954025ad Mon Sep 17 00:00:00 2001 From: Marco Perini Date: Sun, 12 May 2024 18:39:03 +0200 Subject: [PATCH 6/9] fix(proxy-rotation): removed duplicated arg and passed the loader_kwarhs correctly to the node --- examples/openai/proxy.py | 11 +++++++++++ examples/openai/smart_scraper_openai.py | 13 +++++++++++++ scrapegraphai/graphs/abstract_graph.py | 3 +++ scrapegraphai/graphs/smart_scraper_graph.py | 5 ++++- scrapegraphai/nodes/fetch_node.py | 3 +++ scrapegraphai/utils/proxy_rotation.py | 2 +- 6 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 examples/openai/proxy.py diff --git a/examples/openai/proxy.py b/examples/openai/proxy.py new file mode 100644 index 00000000..6572a6e8 --- /dev/null +++ b/examples/openai/proxy.py @@ -0,0 +1,11 @@ +from scrapegraphai.utils import search_proxy_servers + +proxies = search_proxy_servers( + anonymous=True, + countryset={"IT"}, + # secure=True, + timeout=1.0, + max_shape=2 +) + +print(proxies) \ No newline at end of file diff --git a/examples/openai/smart_scraper_openai.py b/examples/openai/smart_scraper_openai.py index 32a1942b..d8adb548 100644 --- a/examples/openai/smart_scraper_openai.py +++ b/examples/openai/smart_scraper_openai.py @@ -22,6 +22,19 @@ "model": "gpt-3.5-turbo", }, "verbose": True, + "headless": False, + "loader_kwargs": { + "proxy" : { + "server": "broker", + "criteria": { + "anonymous": True, + # "secure": True, + "countryset": {"IT"}, + "timeout": 5.0, + "max_shape": 2 + }, + }, + } } # ************************************************ diff --git a/scrapegraphai/graphs/abstract_graph.py b/scrapegraphai/graphs/abstract_graph.py index a71508a4..38eab97f 100644 --- a/scrapegraphai/graphs/abstract_graph.py +++ b/scrapegraphai/graphs/abstract_graph.py @@ -58,8 +58,11 @@ def __init__(self, prompt: str, config: dict, source: Optional[str] = None): "verbose", False) self.headless = True if config is None else config.get( "headless", True) + self.loader_kwargs = config.get("loader_kwargs", {}) + common_params = {"headless": self.headless, "verbose": self.verbose, + "loader_kwargs": self.loader_kwargs, "llm_model": self.llm_model, "embedder_model": self.embedder_model} self.set_common_params(common_params, overwrite=False) diff --git a/scrapegraphai/graphs/smart_scraper_graph.py b/scrapegraphai/graphs/smart_scraper_graph.py index a9e63823..cef674a3 100644 --- a/scrapegraphai/graphs/smart_scraper_graph.py +++ b/scrapegraphai/graphs/smart_scraper_graph.py @@ -57,7 +57,10 @@ def _create_graph(self) -> BaseGraph: """ fetch_node = FetchNode( input="url | local_dir", - output=["doc"] + output=["doc"], + node_config={ + "loader_kwargs": self.config.get("loader_kwargs", {}), + } ) parse_node = ParseNode( input="doc", diff --git a/scrapegraphai/nodes/fetch_node.py b/scrapegraphai/nodes/fetch_node.py index 73363917..baf48615 100644 --- a/scrapegraphai/nodes/fetch_node.py +++ b/scrapegraphai/nodes/fetch_node.py @@ -49,6 +49,9 @@ def __init__( self.verbose = ( False if node_config is None else node_config.get("verbose", False) ) + self.loader_kwargs = ( + {} if node_config is None else node_config.get("loader_kwargs", {}) + ) def execute(self, state): """ diff --git a/scrapegraphai/utils/proxy_rotation.py b/scrapegraphai/utils/proxy_rotation.py index 0ca204e0..e3421cc1 100644 --- a/scrapegraphai/utils/proxy_rotation.py +++ b/scrapegraphai/utils/proxy_rotation.py @@ -161,7 +161,7 @@ def _search_proxy(proxy: Proxy) -> ProxySettings: Returns: A 'playwright' compliant proxy configuration. """ - server = search_proxy_servers(max_shape=1, **proxy.get("criteria", {}))[0] + server = search_proxy_servers(**proxy.get("criteria", {}))[0] return {"server": server} From 5d6d996e8f6132101d4c3af835d74f0674baffa1 Mon Sep 17 00:00:00 2001 From: Marco Perini Date: Mon, 13 May 2024 07:26:43 +0200 Subject: [PATCH 7/9] fix(proxy-rotation): removed max_shape duplicate --- examples/openai/proxy.py | 11 ----------- examples/openai/smart_scraper_openai.py | 6 +++--- scrapegraphai/utils/proxy_rotation.py | 8 +++++++- 3 files changed, 10 insertions(+), 15 deletions(-) delete mode 100644 examples/openai/proxy.py diff --git a/examples/openai/proxy.py b/examples/openai/proxy.py deleted file mode 100644 index 6572a6e8..00000000 --- a/examples/openai/proxy.py +++ /dev/null @@ -1,11 +0,0 @@ -from scrapegraphai.utils import search_proxy_servers - -proxies = search_proxy_servers( - anonymous=True, - countryset={"IT"}, - # secure=True, - timeout=1.0, - max_shape=2 -) - -print(proxies) \ No newline at end of file diff --git a/examples/openai/smart_scraper_openai.py b/examples/openai/smart_scraper_openai.py index d8adb548..e5b5cd5d 100644 --- a/examples/openai/smart_scraper_openai.py +++ b/examples/openai/smart_scraper_openai.py @@ -28,10 +28,10 @@ "server": "broker", "criteria": { "anonymous": True, - # "secure": True, + "secure": True, "countryset": {"IT"}, - "timeout": 5.0, - "max_shape": 2 + "timeout": 10.0, + "max_shape": 3 }, }, } diff --git a/scrapegraphai/utils/proxy_rotation.py b/scrapegraphai/utils/proxy_rotation.py index e3421cc1..9938f168 100644 --- a/scrapegraphai/utils/proxy_rotation.py +++ b/scrapegraphai/utils/proxy_rotation.py @@ -161,7 +161,13 @@ def _search_proxy(proxy: Proxy) -> ProxySettings: Returns: A 'playwright' compliant proxy configuration. """ - server = search_proxy_servers(**proxy.get("criteria", {}))[0] + + + # remove max_shape from criteria + criteria = proxy.get("criteria", {}).copy() + criteria.pop("max_shape", None) + + server = search_proxy_servers(max_shape=1, **criteria)[0] return {"server": server} From e256b758b2ada641f97b23b1cf6c6b0174563d8a Mon Sep 17 00:00:00 2001 From: Marco Perini Date: Mon, 13 May 2024 11:03:33 +0200 Subject: [PATCH 8/9] docs(refactor): added proxy-rotation usage and refactor readthedocs --- docs/assets/searchgraph.png | Bin 0 -> 51393 bytes docs/assets/smartscrapergraph.png | Bin 0 -> 59601 bytes docs/assets/speechgraph.png | Bin 0 -> 46889 bytes docs/source/conf.py | 1 - docs/source/getting_started/examples.rst | 7 +- docs/source/getting_started/installation.rst | 21 +- docs/source/index.rst | 19 +- docs/source/introduction/contributing.rst | 2 +- docs/source/introduction/overview.rst | 27 +-- docs/source/modules/modules.rst | 3 - docs/source/modules/yosoai.graphs.rst | 29 --- docs/source/modules/yosoai.nodes.rst | 61 ------ docs/source/modules/yosoai.rst | 110 ----------- docs/source/scrapers/graph_config.rst | 49 +++++ docs/source/scrapers/graphs.rst | 109 +++++++++++ docs/source/scrapers/llm.rst | 190 +++++++++++++++++++ 16 files changed, 398 insertions(+), 230 deletions(-) create mode 100644 docs/assets/searchgraph.png create mode 100644 docs/assets/smartscrapergraph.png create mode 100644 docs/assets/speechgraph.png delete mode 100644 docs/source/modules/yosoai.graphs.rst delete mode 100644 docs/source/modules/yosoai.nodes.rst delete mode 100644 docs/source/modules/yosoai.rst create mode 100644 docs/source/scrapers/graph_config.rst create mode 100644 docs/source/scrapers/graphs.rst create mode 100644 docs/source/scrapers/llm.rst diff --git a/docs/assets/searchgraph.png b/docs/assets/searchgraph.png new file mode 100644 index 0000000000000000000000000000000000000000..f57c652e45ed83050f8abf681a68bf53525fb66e GIT binary patch literal 51393 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 z zf}K5+njfb&nUL6rO`Jeg!<_#|Mh~1)6)AEoX73m^SNMPuA|BM-bu;7Uspn}5H7(W! z=WD{ucGTItceV3(A^r7; zaXc*|3mKf#$IRv}W6)}&;vD1p(xz_DJ~ulKm|e1E;=~=i8IU0qx;#1iU|Q#{{!cCx z#4eNc{bDPX71FZl3m>d@AsYN#Zqzn&Btb~2hIH(SU+1>GB@P#fZvZEC5O#?MW`Z6v++D_h0HqG#FA>782D4N${W z+ai0!Cm}wcf@F*MfWo57m6WdLSaQ91K3>f!%Hl${d6wu>m>DwVma4i0L(1hIC;44O zT#Jrla_7ZRL*hRBLd^kUl=I#`-~wD+_gZPqykEW11NRPozuF?0_KD3V(7S~{2rj-T zrmimbaaBNqVazGDsP&{%G-a5gYxZxEjN9UQ`Ca8^T(z=AGx6%8>s;f4%L{4~LljNn zW~kR+#0$$Y<21$NVJ=(+l9*Q1jmR$Af~P_Fx%ONG- zI063A_U1gha9AltW^jJ5qWwqAVc)&+;wmp&PtWqi)#xU~lLm?c6txp*Hq;X~Bw&

Dr@`@sFvXl+Lv__LIo3kIk^-05li!Fkk()mxXqng2~Wt@2a# z8BNX&>_aSY&$GU%`~go922kn8$jy%x7NUySQ2lwc^9Q{i5~q%3vGp|W`u%NvYVUeS zTnH^7!2p&LC_vs1Lx%=2vj0Wmy(`%8CYE?TmHu1^1Sm^m;)tGi+oBB7d9|i25u|kw zQ{x~8Byz#Xekbv2BkD8%qE<)fQ7Lw4;mv0fraP|!6av43>+GnNED1Y9bWUyJmbc5x zrsb{gOrM{9pfsz63rAs=o0y&ZNmecbufyQdC{(gyAXhb!3U(i8`tAMfvgw5%>3*m^ zs(QqXEZ9I?x($X50NT(PI0KSsiVb1_*w&O}c%^R!q7~F{=WX$-Od~AGyw$6P>0YkO zM<(^!ayT{L;zDNo2B8a&PTn< zQ$(S`v;d!{$kboo1Fg?IcovX8lbY7ktQJ_zcK~BFYDcng5p+4LC(?Apm*u17=yCJ5 z{?CaR#C5>Hu_V*hF2}6$4zmhyH)#1F%6vI^a}g=aA1b!qA(Mjh@8do8zH31&SQ1*z z)84gEut{nyF?(M3e)-@>qOdcn=$mHw+lV}UwO{J@&3Urs4nui>HRaxcQs4R6+`%&S z^RQl-DWZVllIkM%KxR2?VvU(@M!|EZOk?|B8REwaXhI^F! z7Ae=b5OYq8HqFpTY%{GoCuXT0zzn2|!om=4Ey+_*+VOX%uxR>pxrQhXN7^~oOuW{ilDLF!MI?(jG1xMP_+J*Hm6UK!j zqPornWRGR~Pw}M`u7>efR>+$16)P`-UfZIcV(`(PxVdbk7NcmJ%!%AOTO0pZ=$d4+ znVGVmv=txAo(z*sdk*Y_#>Q?JC|q6bnBy?!2nQ@sjrc0Q#paTd>Rff7dvS0}=kel2 z;RX`wwin4jfV$3{@I88fwE3}ndB`)QC8-`Uz4cB7i%X3tvgaD2LiXgB%#^IQ23uVu zG(}E1ZYcUDngLT*cS2pi(qA0w%wFY+^CDT*Pgp_2kH`+Eyq zThi3LqJp4w({Fd4AswQ&v-0Ky<>TqyK!)C#Hfvg6l4+lFylBV|!9 z*V`1M<#^ixV%F87%*l!u$p|}^T13aq%%J_G_5G%pe3AS34QNcl_l#dmBX@Fov(jBX z%efiLk!Uw`T;O$p{iM~1$ER0k#czYApSF-a=Q;D_eaL~yM;5k<*QxS1(2jC)@~S^b zTGQHffLc<$a^3_P>OIl6cCK~uM16FbkO5KO6ZIpKGOvktu4m%+lh&!mZ%!Wu!bT2M z>*@YgulsYstWZOwZ^%6Ih_ptPA;M`mtQ4(O+_hi%+Z@~nt$NJ6ACDAECEau&tt4LA zii;%AbKaj*7fi$-hSw zJPC{SkGb^fi>H_V5DF%)ZcQT8*Ljm4o;^30cYNjKKi~-Ee7fGWLJGGC$HV2@sH7eN z<-yJB3+yLdgUH9 z#b{R;FLCzjJJ|Fh8LFmGPT$_Y7aMJQt=9sg{V5r2lKl^nrC_!Q**qOc3tK4G|0(Rro z_LPCl)2Z`_aQ4dSa~M4ssa~p^2weJRIwOO<`i!{C52El56-&=c7tH7}zkk;T&8We* z0cYe4sh#}B=_-u%5xEp8C>k#=9hbnLNo%p;bN1o$Q0EsK;u<~ zkXc^OEblO{0Lb~Y$Ep9Z5GS0KL!IO^1)n<;a7G0PsQGc4_ahEsM6Z(s+IxAA+Y0vb zc)Gk+U7qkqtJ!~Kh&Bl*`_%+~EjhUzM!p`r<#wrGG$F1wY>k17SGji}n6GK@Cd*-e zVR`~;^~{TY9<{bECT8q~6r1l-7PpFHNu z8$6M>(Fm^DMxmlRh2Ytlt)#={V`G|tO3A<*eL zulq-Bb$1ye-xH8upgwbBN%eU)Jv|rj22rRwq_oiyLM1@y^|`c48~NG+zjs&Z0X_#w z3*h~>I(980Z9fdubXn{IX?GlvcZP0M)Q(& zRxcIU3tr8QX>+G1O$YgHi9nmAA9Bnr#2XM?y*)ahm=WH+Qq{o1@c6KGv8K_!{5QJ% z{(Or8i7k*fmd?z|;_A-+_RpDAp{oA}g$@+qaw1u|@HRa17v!7@^0V?=&wJbvK5ic_ z7e<%Vo~7Jj)W#J(Lge`3A@Zud)5z1;#h2CN0pAC1t+tT)%zxMnTSFrp`-(Yc1|{$B zl!IJ=L*rI@-$u*-ve@9`WTP#p&%*nCSsmenk?YaQkX2;^pG#*C77@6cc>o4&!}~=Z hp^bXo=rd6~zx=hJbJtmy{fJkfuy#)SS>O4c`%h0$4^{vG literal 0 HcmV?d00001 diff --git a/docs/assets/smartscrapergraph.png b/docs/assets/smartscrapergraph.png new file mode 100644 index 0000000000000000000000000000000000000000..021531a3f4ca50f51b49f25963074fa9729caf14 GIT binary patch literal 59601 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

KBQuQ|gxfsCTLeqm1!8k5J*Vf_ zeRw%ybN1$3Ax5)xk?tTR_6JbDdqjKc{eq8EvJ80(J;&X-CeE$YuvIbQ#p(UJ&k5zP z%X+w9lVee-n=q8{nk#}GS(UfAxpDOoQ8(+$+&5=U~Bu-^aqgQ*=v8o9p zHa~=0D;YS3)`8>PE!vbiI3=6v1ggnP8E0jHX8jftR*iB}yx)#4E1D6@c>8XYqI1(6 z?MhY&En6hSw^nL-nbczNs)7B;+OSt;bcZmP0<_57j8Qyn9PbQQ|)Xi+` z1wbqoyK#mfCvAn+Xi6^yImEJvF<+x85Y1)=D~SKjE3gPA%NXxkfyuvYPVoV`siUh@ z@!ttViCJKrep&Vz{yD61Wva@NEn+eF00Q{kdQt1z&=NMfaaFJ6babBgD6u&m-glHD zRyP63*F$Lq;C59rJ)wGFkW7shnO;$znd82xGjdwsRiN!th8)W}Y*bwv01&4){Fp7$ ziV|A3K+{}G6X~Uw)~;(;s^*43ESMVUWUiwuc0V z28NRG5y-xw+-QZhP4`@Ck9dg@Aa%d!DL?Gn;bv?WB5->-wP&FD4t(b!l$A~^GU$*I zEyVQfc80tqGE+3Zh78hRS=azpQv1nNC5y-6f3ha{Hb#zx5kl~Y;QvQ2J%(i{_SZkUlu{kLwOB{hY-KCKi;uJ+52gr|?RqD#qpj!&7E`>_# zv;Q<8Euy9af(>O%62t{)`E@T)>wveN+4#d|lbMf|{E!T{P616G_{zC=%xQ<0{KS~? zA{$OkMZ~zRT@G&4X>C&>1y{v)W{BdDvH&C&L^tlirM=)oM^K8ZI1oE`{BJTE$|x;0xgPC`ODq;rSRxLMKHQ|5-=h=@`P Ni1qoZU%meL{{YKk%u4_O literal 0 HcmV?d00001 diff --git a/docs/assets/speechgraph.png b/docs/assets/speechgraph.png new file mode 100644 index 0000000000000000000000000000000000000000..70b130628871425a20b8ab2b60a7ee4e0f4e052d GIT binary patch literal 46889 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

%VpcwAz_qpNm_K>lC=@qy81{g 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≦I&c1L>=@uj^iJzR3g&%$X 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 EFQ@oE)c^nh literal 0 HcmV?d00001 diff --git a/docs/source/conf.py b/docs/source/conf.py index 8c46d4c2..3f323d6a 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -30,4 +30,3 @@ # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output html_theme = 'sphinx_rtd_theme' -html_static_path = ['_static'] diff --git a/docs/source/getting_started/examples.rst b/docs/source/getting_started/examples.rst index b6e2eb36..b406f7b3 100644 --- a/docs/source/getting_started/examples.rst +++ b/docs/source/getting_started/examples.rst @@ -1,7 +1,9 @@ Examples ======== -Here some example of the different ways to scrape with ScrapegraphAI +Let's suppose you want to scrape a website to get a list of projects with their descriptions. +You can use the `SmartScraperGraph` class to do that. +The following examples show how to use the `SmartScraperGraph` class with OpenAI models and local models. OpenAI models ^^^^^^^^^^^^^ @@ -78,7 +80,7 @@ After that, you can run the following code, using only your machine resources br # ************************************************ smart_scraper_graph = SmartScraperGraph( - prompt="List me all the news with their description.", + prompt="List me all the projects with their description.", # also accepts a string with the already downloaded HTML code source="https://perinim.github.io/projects", config=graph_config @@ -87,3 +89,4 @@ After that, you can run the following code, using only your machine resources br result = smart_scraper_graph.run() print(result) +To find out how you can customize the `graph_config` dictionary, by using different LLM and adding new parameters, check the `Scrapers` section! \ No newline at end of file diff --git a/docs/source/getting_started/installation.rst b/docs/source/getting_started/installation.rst index 3bca044b..3e40f1c3 100644 --- a/docs/source/getting_started/installation.rst +++ b/docs/source/getting_started/installation.rst @@ -7,26 +7,35 @@ for this project. Prerequisites ^^^^^^^^^^^^^ -- `Python 3.8+ `_ -- `pip ` -- `ollama ` *optional for local models +- `Python >=3.9,<3.12 `_ +- `pip `_ +- `Ollama `_ (optional for local models) Install the library ^^^^^^^^^^^^^^^^^^^^ +The library is available on PyPI, so it can be installed using the following command: + .. code-block:: bash pip install scrapegraphai +**Note:** It is higly recommended to install the library in a virtual environment (conda, venv, etc.) + +If your clone the repository, you can install the library using `poetry `_: + +.. code-block:: bash + + poetry install + Additionally on Windows when using WSL ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +If you are using Windows Subsystem for Linux (WSL) and you are facing issues with the installation of the library, you might need to install the following packages: + .. code-block:: bash sudo apt-get -y install libnss3 libnspr4 libgbm1 libasound2 -As simple as that! You are now ready to scrape gnamgnamgnam 👿👿👿 - - diff --git a/docs/source/index.rst b/docs/source/index.rst index 712bb7c3..ab0c6180 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -3,12 +3,6 @@ You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -Welcome to scrapegraphai-ai's documentation! -======================================= - -Here you will find all the information you need to get started. -The following sections will guide you through the installation process and the usage of the library. - .. toctree:: :maxdepth: 2 :caption: Introduction @@ -22,6 +16,19 @@ The following sections will guide you through the installation process and the u getting_started/installation getting_started/examples + +.. toctree:: + :maxdepth: 2 + :caption: Scrapers + + scrapers/graphs + scrapers/llm + scrapers/graph_config + +.. toctree:: + :maxdepth: 2 + :caption: Modules + modules/modules Indices and tables diff --git a/docs/source/introduction/contributing.rst b/docs/source/introduction/contributing.rst index dd0d529a..75f5adab 100644 --- a/docs/source/introduction/contributing.rst +++ b/docs/source/introduction/contributing.rst @@ -2,7 +2,7 @@ Contributing ============ Hey, you want to contribute? Awesome! -Just fork the repo, make your changes, and send me a pull request. +Just fork the repo, make your changes, and send a pull request. If you're not sure if it's a good idea, open an issue and we'll discuss it. Go and check out the `contributing guidelines `__ for more information. diff --git a/docs/source/introduction/overview.rst b/docs/source/introduction/overview.rst index 46ed21a5..ffb0a5b3 100644 --- a/docs/source/introduction/overview.rst +++ b/docs/source/introduction/overview.rst @@ -1,20 +1,25 @@ +.. image:: ../../assets/scrapegraphai_logo.png + :align: center + :width: 50% + :alt: ScrapegraphAI + Overview ======== -In a world where web pages are constantly changing and in a data-hungry world there is a need for a new generation of scrapers, and this is where ScrapegraphAI was born. -An opensource library with the aim of starting a new era of scraping tools that are more flexible and require less maintenance by developers, with the use of LLMs. +ScrapeGraphAI is a open-source web scraping python library designed to usher in a new era of scraping tools. +In today's rapidly evolving and data-intensive digital landscape, this library stands out by integrating LLM and +direct graph logic to automate the creation of scraping pipelines for websites and various local documents, including XML, +HTML, JSON, and more. -.. image:: ../../assets/scrapegraphai_logo.png - :align: center - :width: 100px - :alt: ScrapegraphAI +Simply specify the information you need to extract, and ScrapeGraphAI handles the rest, +providing a more flexible and low-maintenance solution compared to traditional scraping tools. Why ScrapegraphAI? ================== -ScrapegraphAI in our vision represents a significant step forward in the field of web scraping, offering an open-source solution designed to meet the needs of a constantly evolving web landscape. Here's why ScrapegraphAI stands out: - -Flexibility and Adaptability -^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Traditional web scraping tools often rely on fixed patterns or manual configuration to extract data from web pages. ScrapegraphAI, leveraging the power of LLMs, adapts to changes in website structures, reducing the need for constant developer intervention. +Traditional web scraping tools often rely on fixed patterns or manual configuration to extract data from web pages. +ScrapegraphAI, leveraging the power of LLMs, adapts to changes in website structures, reducing the need for constant developer intervention. This flexibility ensures that scrapers remain functional even when website layouts change. + +We support many Large Language Models (LLMs) including GPT, Gemini, Groq, Azure, Hugging Face etc. +as well as local models which can run on your machine using Ollama. \ No newline at end of file diff --git a/docs/source/modules/modules.rst b/docs/source/modules/modules.rst index eaa8b0f6..f22d1cea 100644 --- a/docs/source/modules/modules.rst +++ b/docs/source/modules/modules.rst @@ -1,6 +1,3 @@ -scrapegraphai -============= - .. toctree:: :maxdepth: 4 diff --git a/docs/source/modules/yosoai.graphs.rst b/docs/source/modules/yosoai.graphs.rst deleted file mode 100644 index 5d096474..00000000 --- a/docs/source/modules/yosoai.graphs.rst +++ /dev/null @@ -1,29 +0,0 @@ -scrapegraphai.graphs package -===================== - -Submodules ----------- - -scrapegraphai.graphs.base\_graph module --------------------------------- - -.. automodule:: scrapegraphai.graphs.base_graph - :members: - :undoc-members: - :show-inheritance: - -scrapegraphai.graphs.smart\_scraper\_graph module ------------------------------------------- - -.. automodule:: scrapegraphai.graphs.smart_scraper_graph - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: scrapegraphai.graphs - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/modules/yosoai.nodes.rst b/docs/source/modules/yosoai.nodes.rst deleted file mode 100644 index 167f83fa..00000000 --- a/docs/source/modules/yosoai.nodes.rst +++ /dev/null @@ -1,61 +0,0 @@ -scrapegraphai.nodes package -==================== - -Submodules ----------- - -scrapegraphai.nodes.base\_node module ------------------------------- - -.. automodule:: scrapegraphai.nodes.base_node - :members: - :undoc-members: - :show-inheritance: - -scrapegraphai.nodes.conditional\_node module -------------------------------------- - -.. automodule:: scrapegraphai.nodes.conditional_node - :members: - :undoc-members: - :show-inheritance: - -scrapegraphai.nodes.fetch\_html\_node module -------------------------------------- - -.. automodule:: scrapegraphai.nodes.fetch_html_node - :members: - :undoc-members: - :show-inheritance: - -scrapegraphai.nodes.generate\_answer\_node module ------------------------------------------- - -.. automodule:: scrapegraphai.nodes.generate_answer_node - :members: - :undoc-members: - :show-inheritance: - -scrapegraphai.nodes.get\_probable\_tags\_node module ---------------------------------------------- - -.. automodule:: scrapegraphai.nodes.get_probable_tags_node - :members: - :undoc-members: - :show-inheritance: - -scrapegraphai.nodes.parse\_html\_node module -------------------------------------- - -.. automodule:: scrapegraphai.nodes.parse_html_node - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: scrapegraphai.nodes - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/modules/yosoai.rst b/docs/source/modules/yosoai.rst deleted file mode 100644 index 43251cb3..00000000 --- a/docs/source/modules/yosoai.rst +++ /dev/null @@ -1,110 +0,0 @@ -scrapegraphai package -============== - -Subpackages ------------ - -.. toctree:: - :maxdepth: 4 - - scrapegraphai.graphs - scrapegraphai.nodes - -Submodules ----------- - -scrapegraphai.class\_creator module ----------------------------- - -.. automodule:: scrapegraphai.class_creator - :members: - :undoc-members: - :show-inheritance: - -scrapegraphai.class\_generator module ------------------------------- - -.. automodule:: scrapegraphai.class_generator - :members: - :undoc-members: - :show-inheritance: - -scrapegraphai.convert\_to\_csv module ------------------------------- - -.. automodule:: scrapegraphai.convert_to_csv - :members: - :undoc-members: - :show-inheritance: - -scrapegraphai.convert\_to\_json module -------------------------------- - -.. automodule:: scrapegraphai.convert_to_json - :members: - :undoc-members: - :show-inheritance: - -scrapegraphai.dictionaries module --------------------------- - -.. automodule:: scrapegraphai.dictionaries - :members: - :undoc-members: - :show-inheritance: - -scrapegraphai.getter module --------------------- - -.. automodule:: scrapegraphai.getter - :members: - :undoc-members: - :show-inheritance: - -scrapegraphai.json\_getter module --------------------------- - -.. automodule:: scrapegraphai.json_getter - :members: - :undoc-members: - :show-inheritance: - -scrapegraphai.pydantic\_class module ------------------------------ - -.. automodule:: scrapegraphai.pydantic_class - :members: - :undoc-members: - :show-inheritance: - -scrapegraphai.remover module ---------------------- - -.. automodule:: scrapegraphai.remover - :members: - :undoc-members: - :show-inheritance: - -scrapegraphai.request module ---------------------- - -.. automodule:: scrapegraphai.request - :members: - :undoc-members: - :show-inheritance: - -scrapegraphai.token\_calculator module -------------------------------- - -.. automodule:: scrapegraphai.token_calculator - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: scrapegraphai - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/scrapers/graph_config.rst b/docs/source/scrapers/graph_config.rst new file mode 100644 index 00000000..a5ade9c5 --- /dev/null +++ b/docs/source/scrapers/graph_config.rst @@ -0,0 +1,49 @@ +Additional Parameters +===================== + +It is possible to customize the behavior of the graphs by setting some configuration options. +Some interesting ones are: + +- `verbose`: If set to `True`, some debug information will be printed to the console. +- `headless`: If set to `False`, the web browser will be opened on the URL requested and close right after the HTML is fetched. +- `max_results`: The maximum number of results to be fetched from the search engine. Useful in `SearchGraph`. +- `output_path`: The path where the output files will be saved. Useful in `SpeechGraph`. + +Proxy Rotation +^^^^^^^^^^^^^^ + +It is possible to rotate the proxy by setting the `proxy` option in the graph configuration. +We provide a free proxy service which is based on `free-proxy `_ library and can be used as follows: + +.. code-block:: python + + graph_config = { + "llm":{...}, + "loader_kwargs": { + "proxy" : { + "server": "broker", + "criteria": { + "anonymous": True, + "secure": True, + "countryset": {"IT"}, + "timeout": 10.0, + "max_shape": 3 + }, + }, + }, + } + +Do you have a proxy server? You can use it as follows: + +.. code-block:: python + + graph_config = { + "llm":{...}, + "loader_kwargs": { + "proxy" : { + "server": "http://your_proxy_server:port", + "username": "your_username", + "password": "your_password", + }, + }, + } diff --git a/docs/source/scrapers/graphs.rst b/docs/source/scrapers/graphs.rst new file mode 100644 index 00000000..efd87537 --- /dev/null +++ b/docs/source/scrapers/graphs.rst @@ -0,0 +1,109 @@ +Graphs +====== + +Graphs are scraping pipelines aimed at solving specific tasks. They are composed by nodes which can be configured individually to address different aspects of the task (fetching data, extracting information, etc.). + +There are currently three types of graphs available in the library: + +- **SmartScraperGraph**: one-page scraper that requires a user-defined prompt and a URL (or local file) to extract information from using LLM. +- **SearchGraph**: multi-page scraper that only requires a user-defined prompt to extract information from a search engine using LLM. It is built on top of SmartScraperGraph. +- **SpeechGraph**: text-to-speech pipeline that generates an answer as well as a requested audio file. It is built on top of SmartScraperGraph and requires a user-defined prompt and a URL (or local file). + +**Note:** they all use a graph configuration to set up LLM models and other parameters. To find out more about the configurations, check the `LLM`_ and `Configuration`_ sections. + +SmartScraperGraph +^^^^^^^^^^^^^^^^^ + +.. image:: ../../assets/smartscrapergraph.png + :align: center + :width: 90% + :alt: SmartScraperGraph +| + +First we define the graph configuration, which includes the LLM model and other parameters. Then we create an instance of the SmartScraperGraph class, passing the prompt, source, and configuration as arguments. Finally, we run the graph and print the result. +It will fetch the data from the source and extract the information based on the prompt in JSON format. + +.. code-block:: python + + from scrapegraphai.graphs import SmartScraperGraph + + graph_config = { + "llm": {...}, + } + + smart_scraper_graph = SmartScraperGraph( + prompt="List me all the projects with their descriptions", + source="https://perinim.github.io/projects", + config=graph_config + ) + + result = smart_scraper_graph.run() + print(result) + + +SearchGraph +^^^^^^^^^^^ + +.. image:: ../../assets/searchgraph.png + :align: center + :width: 80% + :alt: SearchGraph +| + +Similar to SmartScraperGraph, we define the graph configuration, create an instance of the SearchGraph class, and run the graph. +It will create a search query, fetch the first n results from the search engine, run n SmartScraperGraph instances, and return the results in JSON format. + + +.. code-block:: python + + from scrapegraphai.graphs import SearchGraph + + graph_config = { + "llm": {...}, + "embeddings": {...}, + } + + # Create the SearchGraph instance + search_graph = SearchGraph( + prompt="List me all the traditional recipes from Chioggia", + config=graph_config + ) + + # Run the graph + result = search_graph.run() + print(result) + + +SpeechGraph +^^^^^^^^^^^ + +.. image:: ../../assets/speechgraph.png + :align: center + :width: 90% + :alt: SpeechGraph +| + +Similar to SmartScraperGraph, we define the graph configuration, create an instance of the SpeechGraph class, and run the graph. +It will fetch the data from the source, extract the information based on the prompt, and generate an audio file with the answer, as well as the answer itself, in JSON format. + +.. code-block:: python + + from scrapegraphai.graphs import SpeechGraph + + graph_config = { + "llm": {...}, + "tts_model": {...}, + } + + # ************************************************ + # Create the SpeechGraph instance and run it + # ************************************************ + + speech_graph = SpeechGraph( + prompt="Make a detailed audio summary of the projects.", + source="https://perinim.github.io/projects/", + config=graph_config, + ) + + result = speech_graph.run() + print(result) \ No newline at end of file diff --git a/docs/source/scrapers/llm.rst b/docs/source/scrapers/llm.rst new file mode 100644 index 00000000..486668b1 --- /dev/null +++ b/docs/source/scrapers/llm.rst @@ -0,0 +1,190 @@ +LLM +=== + +We support many known LLM models and providers used to analyze the web pages and extract the information requested by the user. Models can be split in **Chat Models** and **Embedding Models** (the latter are mainly used for Retrieval Augmented Generation RAG). +These models are specified inside the graph configuration dictionary and can be used interchangeably, for example by defining a different model for llm and embeddings. + +- **Local Models**: These models are hosted on the local machine and can be used without any API key. +- **API-based Models**: These models are hosted on the cloud and require an API key to access them (eg. OpenAI, Groq, etc). + +**Note**: If the emebedding model is not specified, the library will use the default one for that LLM, if available. + +Local Models +------------ + +Currently, local models are supported through Ollama integration. Ollama is a provider of LLM models which can be downloaded from here `Ollama `_. +Let's say we want to use **llama3** as chat model and **nomic-embed-text** as embedding model. We first need to pull them from ollama using: + +.. code-block:: bash + + ollama pull llama3 + ollama pull nomic-embed-text + +Then we can use them in the graph configuration as follows: + +.. code-block:: python + + graph_config = { + "llm": { + "model": "llama3", + "temperature": 0.0, + "format": "json", + }, + "embeddings": { + "model": "nomic-embed-text", + }, + } + +You can also specify the **base_url** parameter to specify the models endpoint. By default, it is set to http://localhost:11434. This is useful if you are running Ollama on a Docker container or on a different machine. + +If you want to host Ollama in a Docker container, you can use the following command: + +.. code-block:: bash + + docker-compose up -d + docker exec -it ollama ollama pull llama3 + +API-based Models +---------------- + +OpenAI +^^^^^^ + +You can get the API key from `here `_. + +.. code-block:: python + + graph_config = { + "llm": { + "api_key": "OPENAI_API_KEY", + "model": "gpt-3.5-turbo", + }, + } + +If you want to use text to speech models, you can specify the `tts_model` parameter: + +.. code-block:: python + + graph_config = { + "llm": { + "api_key": "OPENAI_API_KEY", + "model": "gpt-3.5-turbo", + "temperature": 0.7, + }, + "tts_model": { + "api_key": "OPENAI_API_KEY", + "model": "tts-1", + "voice": "alloy" + }, + } + +Gemini +^^^^^^ + +You can get the API key from `here `_. + +**Note**: some countries are not supported and therefore it won't be possible to request an API key. A possible workaround is to use a VPN or run the library on Colab. + +.. code-block:: python + + graph_config = { + "llm": { + "api_key": "GEMINI_API_KEY", + "model": "gemini-pro" + }, + } + +Groq +^^^^ + +You can get the API key from `here `_. Groq doesn't support embedding models, so in the following example we are using Ollama one. + +.. code-block:: python + + graph_config = { + "llm": { + "model": "groq/gemma-7b-it", + "api_key": "GROQ_API_KEY", + "temperature": 0 + }, + "embeddings": { + "model": "ollama/nomic-embed-text", + }, + } + +Azure +^^^^^ + +We can also pass a model instance for the chat model and the embedding model. For Azure, a possible configuration would be: + +.. code-block:: python + + llm_model_instance = AzureChatOpenAI( + openai_api_version="AZURE_OPENAI_API_VERSION", + azure_deployment="AZURE_OPENAI_CHAT_DEPLOYMENT_NAME" + ) + + embedder_model_instance = AzureOpenAIEmbeddings( + azure_deployment="AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT_NAME", + openai_api_version="AZURE_OPENAI_API_VERSION", + ) + + graph_config = { + "llm": { + "model_instance": llm_model_instance + }, + "embeddings": { + "model_instance": embedder_model_instance + } + } + +Hugging Face Hub +^^^^^^^^^^^^^^^^ + +We can also pass a model instance for the chat model and the embedding model. For Hugging Face, a possible configuration would be: + +.. code-block:: python + + llm_model_instance = HuggingFaceEndpoint( + repo_id="mistralai/Mistral-7B-Instruct-v0.2", + max_length=128, + temperature=0.5, + token="HUGGINGFACEHUB_API_TOKEN" + ) + + embedder_model_instance = HuggingFaceInferenceAPIEmbeddings( + api_key="HUGGINGFACEHUB_API_TOKEN", + model_name="sentence-transformers/all-MiniLM-l6-v2" + ) + + graph_config = { + "llm": { + "model_instance": llm_model_instance + }, + "embeddings": { + "model_instance": embedder_model_instance + } + } + +Anthropic +^^^^^^^^^ + +We can also pass a model instance for the chat model and the embedding model. For Anthropic, a possible configuration would be: + +.. code-block:: python + + embedder_model_instance = HuggingFaceInferenceAPIEmbeddings( + api_key="HUGGINGFACEHUB_API_TOKEN", + model_name="sentence-transformers/all-MiniLM-l6-v2" + ) + + graph_config = { + "llm": { + "api_key": "ANTHROPIC_API_KEY", + "model": "claude-3-haiku-20240307", + "max_tokens": 4000 + }, + "embeddings": { + "model_instance": embedder_model_instance + } + } \ No newline at end of file From 0c36a7ec1f32ee073d9e0f534a2cb97aba3d7a1f Mon Sep 17 00:00:00 2001 From: Marco Perini Date: Mon, 13 May 2024 11:04:56 +0200 Subject: [PATCH 9/9] feat: added proxy rotation --- examples/openai/smart_scraper_openai.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/examples/openai/smart_scraper_openai.py b/examples/openai/smart_scraper_openai.py index e5b5cd5d..01448a5b 100644 --- a/examples/openai/smart_scraper_openai.py +++ b/examples/openai/smart_scraper_openai.py @@ -23,18 +23,6 @@ }, "verbose": True, "headless": False, - "loader_kwargs": { - "proxy" : { - "server": "broker", - "criteria": { - "anonymous": True, - "secure": True, - "countryset": {"IT"}, - "timeout": 10.0, - "max_shape": 3 - }, - }, - } } # ************************************************