├── tests ├── __init__.py └── test_htmlextractor.py ├── src └── crawlerflow │ ├── __init__.py │ ├── downloaders │ ├── __init__.py │ └── requests_downloader.py │ ├── exceptions.py │ ├── spiders │ ├── __init__.py │ ├── api_spider.py │ ├── xml_spider.py │ ├── html_spider.py │ └── base.py │ ├── pipelines │ ├── __init__.py │ ├── metadata.py │ ├── json.py │ ├── mongodb.py │ └── default.py │ ├── extractors │ ├── __init__.py │ ├── content.py │ └── base.py │ ├── request.py │ ├── utils.py │ ├── settings.py │ └── runner.py ├── pytest.ini ├── .gitignore ├── .DS_Store ├── example-configs ├── crawlerflow │ ├── requests │ │ ├── github-xml-urls.yml │ │ └── github-detail-urls.yml │ ├── extractors │ │ ├── github-blog-list.yml │ │ └── github-blog-detail.yml │ └── spiders │ │ ├── default-spider.yml │ │ └── default-xml-spider.yml └── webcrawler │ ├── APISpiders │ └── api-publicapis-org.yml │ └── HTMLSpiders │ ├── github-blog-list.yml │ └── github-blog-detail.yml ├── Pipfile ├── runtests.py ├── examples ├── run_xml_crawlerflow.py ├── run_crawlerflow.py └── run_webcrawler.py ├── setup.py ├── conftest.py ├── README.md └── Pipfile.lock /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/crawlerflow/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | pythonpath = src/ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | __pycache__/ 3 | *.log 4 | *.json 5 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/invana/crawlerflow/HEAD/.DS_Store -------------------------------------------------------------------------------- /src/crawlerflow/downloaders/__init__.py: -------------------------------------------------------------------------------- 1 | from .requests_downloader import RequestsDownloaderMiddleware -------------------------------------------------------------------------------- /src/crawlerflow/exceptions.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | class DefaultExtractorRequired(Exception): 6 | pass -------------------------------------------------------------------------------- /example-configs/crawlerflow/requests/github-xml-urls.yml: -------------------------------------------------------------------------------- 1 | - url: https://github.blog/feed/ 2 | - url: https://github.blog/comments/feed/ -------------------------------------------------------------------------------- /src/crawlerflow/spiders/__init__.py: -------------------------------------------------------------------------------- 1 | from .api_spider import APISpider 2 | from .html_spider import HTMLSpider 3 | from .xml_spider import XMLFeedSpider -------------------------------------------------------------------------------- /src/crawlerflow/pipelines/__init__.py: -------------------------------------------------------------------------------- 1 | from .json import JsonWriterPipeline 2 | from .mongodb import MongoDBPipeline 3 | from .metadata import MetadataPipeline -------------------------------------------------------------------------------- /example-configs/crawlerflow/requests/github-detail-urls.yml: -------------------------------------------------------------------------------- 1 | - url: https://github.blog/2023-08-03-introducing-code-referencing-for-github-copilot/ 2 | client: client1 3 | -------------------------------------------------------------------------------- /src/crawlerflow/extractors/__init__.py: -------------------------------------------------------------------------------- 1 | from .content import HTMLExtractor, MetaTagExtractor, JSONLDExtractor, \ 2 | TableContentExtractor, IconsExtractor, FeedUrlsExtractor, ImagesExtractor -------------------------------------------------------------------------------- /src/crawlerflow/spiders/api_spider.py: -------------------------------------------------------------------------------- 1 | from .base import SpiderBase 2 | import json 3 | 4 | 5 | class APISpider(SpiderBase): 6 | 7 | def parse_default_extractor(self, response): 8 | return json.loads(response.body) 9 | -------------------------------------------------------------------------------- /example-configs/crawlerflow/extractors/github-blog-list.yml: -------------------------------------------------------------------------------- 1 | id: github-blog-list 2 | test_urls: 3 | - https://github.blog/category/engineering/ 4 | extractor_type: HTMLExtractor 5 | fields: 6 | blog_links: 7 | selector: ".mb-12px .Link--primary::attr(href)" 8 | type: [StringField] -------------------------------------------------------------------------------- /src/crawlerflow/request.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class CrawlRequest: 4 | 5 | def __init__(self, url, referrer=None, **meta) -> None: 6 | self.url: str = url 7 | self.meta: dict = meta 8 | 9 | def __str__(self) -> str: 10 | return f"" -------------------------------------------------------------------------------- /example-configs/webcrawler/APISpiders/api-publicapis-org.yml: -------------------------------------------------------------------------------- 1 | spider_type: "APISpider" 2 | name: "Public APIs Data" 3 | custom_settings: 4 | USER_AGENT : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36' 5 | start_urls: 6 | - https://api.publicapis.org/entries 7 | -------------------------------------------------------------------------------- /src/crawlerflow/spiders/xml_spider.py: -------------------------------------------------------------------------------- 1 | from scrapy.spiders import XMLFeedSpider as _XMLFeedSpider 2 | import feedparser 3 | from .base import SpiderBase 4 | 5 | 6 | class XMLFeedSpider(SpiderBase): 7 | 8 | 9 | def parse_default_extractor(self, response): 10 | return feedparser.parse(response.body) 11 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | scrapy = "*" 8 | pyyaml = "*" 9 | pymongo = "*" 10 | python-slugify = "*" 11 | feedparser = "*" 12 | 13 | [dev-packages] 14 | ipykernel = "*" 15 | pytest = "*" 16 | 17 | [requires] 18 | python_version = "3.11" 19 | -------------------------------------------------------------------------------- /runtests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | import pytest 4 | 5 | PYTEST_ARGS = ['tests', '--tb=short', '-rw'] 6 | 7 | 8 | def exit_on_failure(command, message=None): 9 | if command: 10 | sys.exit(command) 11 | 12 | 13 | def run_tests_coverage(): 14 | if __name__ == "__main__": 15 | pytest.main(PYTEST_ARGS) 16 | 17 | 18 | exit_on_failure(run_tests_coverage()) -------------------------------------------------------------------------------- /src/crawlerflow/pipelines/metadata.py: -------------------------------------------------------------------------------- 1 | import json 2 | from itemadapter import ItemAdapter 3 | import datetime 4 | 5 | 6 | class MetadataPipeline: 7 | def open_spider(self, spider): 8 | pass 9 | 10 | def close_spider(self, spider): 11 | pass 12 | 13 | def process_item(self, item, spider): 14 | item['meta__ingested_at'] = datetime.datetime.now() 15 | return item -------------------------------------------------------------------------------- /src/crawlerflow/pipelines/json.py: -------------------------------------------------------------------------------- 1 | import json 2 | from itemadapter import ItemAdapter 3 | 4 | 5 | class JsonWriterPipeline: 6 | def open_spider(self, spider): 7 | self.file = open("items.jsonl", "w") 8 | 9 | def close_spider(self, spider): 10 | self.file.close() 11 | 12 | def process_item(self, item, spider): 13 | line = json.dumps(ItemAdapter(item).asdict()) + "\n" 14 | self.file.write(line) 15 | return item -------------------------------------------------------------------------------- /examples/run_xml_crawlerflow.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append("src") 3 | from crawlerflow.runner import Crawlerflow 4 | from crawlerflow.utils import yaml_to_json 5 | 6 | 7 | crawl_requests = yaml_to_json(open("example-configs/crawlerflow/requests/github-xml-urls.yml")) 8 | spider_config = yaml_to_json(open("example-configs/crawlerflow/spiders/default-xml-spider.yml")) 9 | 10 | 11 | flow = Crawlerflow() 12 | flow.add_spider_with_config(crawl_requests, spider_config) 13 | flow.start() -------------------------------------------------------------------------------- /examples/run_crawlerflow.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append("src") 3 | from crawlerflow.runner import Crawlerflow 4 | from crawlerflow.utils import yaml_to_json 5 | 6 | 7 | crawl_requests = yaml_to_json(open("example-configs/crawlerflow/requests/github-detail-urls.yml")) 8 | spider_config = yaml_to_json(open("example-configs/crawlerflow/spiders/default-spider.yml")) 9 | github_default_extractor = yaml_to_json(open("example-configs/crawlerflow/extractors/github-blog-detail.yml")) 10 | 11 | flow = Crawlerflow() 12 | flow.add_spider_with_config(crawl_requests, spider_config, default_extractor=github_default_extractor) 13 | flow.start() -------------------------------------------------------------------------------- /src/crawlerflow/utils.py: -------------------------------------------------------------------------------- 1 | from urllib.parse import urlparse 2 | import uuid 3 | import yaml 4 | 5 | 6 | def get_urn(url): 7 | """ 8 | convert https://blog.scrapinghub.com/page/6/ into blog.scrapinghub.com/page/6/ 9 | :param url: 10 | :return: 11 | """ 12 | if "://" in url: 13 | return url.split("://")[1] 14 | return url 15 | 16 | 17 | def get_domain(url): 18 | url_parsed = urlparse(url) 19 | return url_parsed.netloc 20 | 21 | 22 | def generate_uuid(): 23 | return uuid.uuid4().__str__() 24 | 25 | 26 | def yaml_to_json(yaml_string: str): 27 | return yaml.safe_load(yaml_string) 28 | -------------------------------------------------------------------------------- /examples/run_webcrawler.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append("src") 3 | from crawlerflow.runner import WebCrawler 4 | from crawlerflow.utils import yaml_to_json 5 | 6 | 7 | scraper_config_files = [ 8 | "example-configs/webcrawler/APISpiders/api-publicapis-org.yml", 9 | "example-configs/webcrawler/HTMLSpiders/github-blog-list.yml", 10 | "example-configs/webcrawler/HTMLSpiders/github-blog-detail.yml" 11 | ] 12 | 13 | crawlerflow = WebCrawler() 14 | 15 | for scraper_config_file in scraper_config_files: 16 | scraper_config = yaml_to_json(open(scraper_config_file)) 17 | crawlerflow.add_spider_with_config(scraper_config) 18 | crawlerflow.start() -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup, find_packages 4 | 5 | setup( 6 | name='web-scraper', 7 | version='0.0.0', 8 | description='scrape data from web with no code (just YAML configs)', 9 | author='Ravi Raja Merugu', 10 | author_email='ravi@invana.io', 11 | url='https://github.com/invana/web-scraper', 12 | packages=find_packages( 13 | where="src", 14 | exclude=("dist", "docs", "examples", "tests", "examples-configs") 15 | ), 16 | install_requires=[ 17 | 'Scrapy==2.11.0', 18 | 'PyYAML==6.0.1', 19 | 'pymongo==4.5.0', 20 | 'python-slugify==8.0.1', 21 | 'lxml2json' 22 | ] 23 | ) -------------------------------------------------------------------------------- /example-configs/webcrawler/HTMLSpiders/github-blog-list.yml: -------------------------------------------------------------------------------- 1 | 2 | spider_type: "HTMLSpider" 3 | name: "Github Data" 4 | custom_settings: 5 | USER_AGENT : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36 6 | BOT_NAME: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36 7 | start_urls: 8 | - https://github.blog/category/engineering/ 9 | extra_data: 10 | domain : github.com 11 | default_extractor: 12 | extractor_type: HTMLExtractor 13 | fields: 14 | blogs: 15 | selector: ".mb-12px .Link--primary::attr(href)" 16 | type: [StringField] 17 | # traversals: 18 | # id: "detail-view" -------------------------------------------------------------------------------- /example-configs/crawlerflow/spiders/default-spider.yml: -------------------------------------------------------------------------------- 1 | name: "default-html-spider" 2 | spider_type: HTMLSpider # HTMLSpider|APISpider|XMLFeedSpider 3 | downloader: default # default|requests 4 | custom_settings: 5 | USER_AGENT : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36 6 | BOT_NAME: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36 7 | STORAGE_MONGO_URI: mongodb://root:example@localhost:27017 8 | STORAGE_MONGO_DATABASE: crawlerflow 9 | # by default, collection_name would be generated based on spider name. 10 | STORAGE_MONGO_DEFAULT_COLLECTION: scraped_items 11 | extra_data: 12 | client: client1 13 | callback_urls: 14 | - http://example.com/callback 15 | 16 | -------------------------------------------------------------------------------- /example-configs/crawlerflow/spiders/default-xml-spider.yml: -------------------------------------------------------------------------------- 1 | name: "default-xml-spider" 2 | spider_type: XMLFeedSpider # HTMLSpider|APISpider|XMLFeedSpider 3 | downloader: default # default|requests 4 | custom_settings: 5 | USER_AGENT : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36 6 | BOT_NAME: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36 7 | STORAGE_MONGO_URI: mongodb://root:example@localhost:27017 8 | STORAGE_MONGO_DATABASE: crawlerflow 9 | # by default, collection_name would be generated based on spider name. 10 | STORAGE_MONGO_DEFAULT_COLLECTION: scraped_items 11 | extra_data: 12 | client: client1 13 | callback_urls: 14 | - http://example.com/callback 15 | 16 | -------------------------------------------------------------------------------- /tests/test_htmlextractor.py: -------------------------------------------------------------------------------- 1 | 2 | def test_string_extractor(html_extractor): 3 | result = html_extractor.extract() 4 | assert type(result) is dict 5 | assert result['title'] == 'Hello, Parsel!' 6 | 7 | def test_image_extractor(html_extractor): 8 | result = html_extractor.extract() 9 | assert type(result) is dict 10 | assert result['cover_pic'] == 'https://placehold.co/600x400.png' 11 | 12 | def test_list_of_dict_extractor(html_extractor): 13 | result = html_extractor.extract() 14 | assert type(result) is dict 15 | assert type(result['header_links']) is list 16 | assert len(result['header_links']) == 2 17 | assert result['header_links'][0]['link'] == 'http://example.com' 18 | 19 | def test_href_extractor(html_extractor): 20 | result = html_extractor.extract() 21 | assert result['header_links'][0]['link'] == 'http://example.com' 22 | 23 | -------------------------------------------------------------------------------- /src/crawlerflow/settings.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | DEFAULT_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.76" 4 | 5 | DEFAULT_SETTINGS_OVERRIDES = { 6 | 'LOG_LEVEL' : "INFO", 7 | 'RETRY_TIMES' : 5, 8 | 'COMPRESSION_ENABLED': True, 9 | 'HTTPCACHE_ENABLED': False, 10 | 'USER_AGENT' : DEFAULT_USER_AGENT, 11 | 12 | 'ITEM_PIPELINES': { 13 | 'crawlerflow.pipelines.MetadataPipeline': 0, 14 | 'crawlerflow.pipelines.MongoDBPipeline': 1, 15 | # 'crawlerflow.pipelines.JsonWriterPipeline': 2, 16 | }, 17 | 'DOWNLOADER_MIDDLEWARES': { 18 | "crawlerflow.downloaders.RequestsDownloaderMiddleware": 501, 19 | }, 20 | 21 | 'STORAGE_MONGO_URI': "mongodb://root:example@localhost:27017", 22 | 'STORAGE_MONGO_DATABASE': "crawlerflow", 23 | 'STORAGE_MONGO_DEFAULT_COLLECTION': 'scraped_items' 24 | } -------------------------------------------------------------------------------- /example-configs/crawlerflow/extractors/github-blog-detail.yml: -------------------------------------------------------------------------------- 1 | id: github-blog-detail 2 | test_urls: 3 | - https://github.blog/2023-08-03-introducing-code-referencing-for-github-copilot/ 4 | extractor_type: HTMLExtractor 5 | fields: 6 | title: 7 | selector: "h1::text" 8 | cover_pic: 9 | selector: ".col-lg-10 .cover-image::attr(src)" 10 | categories: 11 | selector: ".post-hero__categories li a::text" 12 | type: [StringField] 13 | author: 14 | type: DictField 15 | selector: ".p-responsive-blog .mr-lg-5" 16 | fields: 17 | name: 18 | selector: "img::attr(alt)" 19 | profile_link: 20 | selector: ".mr-lg-5::attr(href)" 21 | profile_pic: 22 | selector: "img::attr(src)" 23 | post_content_html: 24 | selector: ".post__content" 25 | page_meta: 26 | extractor_type: MetaTagExtractor 27 | icons: 28 | extractor_type: IconsExtractor 29 | json_ld: 30 | extractor_type: JSONLDExtractor 31 | feed_urls: 32 | extractor_type: FeedUrlsExtractor 33 | image_urls: 34 | extractor_type: ImagesExtractor -------------------------------------------------------------------------------- /conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import os 3 | 4 | from crawlerflow.extractors import HTMLExtractor 5 | import yaml 6 | 7 | html_text = """ 8 | 9 | 10 |

Hello, Parsel!

11 | 15 | 16 |
Main text here
17 | 18 | 19 | """ 20 | extractor_config =""" 21 | --- 22 | title: 23 | selector: h1::text 24 | cover_pic: 25 | selector: .banner-pic::attr(src) 26 | header_links: 27 | selector: .header li a 28 | type: 29 | - DictField 30 | fields: 31 | link: 32 | selector: ::attr(href) 33 | text: 34 | selector: ::text 35 | post_content_html: 36 | selector: main 37 | """ 38 | 39 | @pytest.fixture(scope="function") 40 | def html_extractor() -> str: 41 | extractor_config_json = yaml.safe_load(extractor_config) 42 | return HTMLExtractor(html_text, extractor_config_json) -------------------------------------------------------------------------------- /example-configs/webcrawler/HTMLSpiders/github-blog-detail.yml: -------------------------------------------------------------------------------- 1 | 2 | spider_type: "HTMLSpider" 3 | name: "Github Blog Detail" 4 | custom_settings: 5 | USER_AGENT : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36 6 | BOT_NAME: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36 7 | start_urls: 8 | - https://github.blog/2023-08-03-introducing-code-referencing-for-github-copilot/ 9 | extra_data: 10 | domain : github.com 11 | default_extractor: 12 | extractor_type: HTMLExtractor 13 | fields: 14 | title: 15 | selector: "h1::text" 16 | cover_pic: 17 | selector: ".col-lg-10 .cover-image::attr(src)" 18 | categories: 19 | selector: ".post-hero__categories li a::text" 20 | type: [StringField] 21 | author: 22 | type: DictField 23 | selector: ".p-responsive-blog .mr-lg-5" 24 | fields: 25 | name: 26 | selector: "img::attr(alt)" 27 | profile_link: 28 | selector: ".mr-lg-5::attr(href)" 29 | profile_pic: 30 | selector: "img::attr(src)" 31 | post_content_html: 32 | selector: ".post__content" -------------------------------------------------------------------------------- /src/crawlerflow/spiders/html_spider.py: -------------------------------------------------------------------------------- 1 | from .base import SpiderBase 2 | import importlib 3 | 4 | 5 | class HTMLSpider(SpiderBase): 6 | """ 7 | 8 | """ 9 | 10 | def run_extractor(self, response, extractor_type, exctactor_fields=None): 11 | extractor_cls = getattr(importlib.import_module( 12 | f"crawlerflow.extractors"), extractor_type) 13 | extractor = extractor_cls(response, extractor_fields=exctactor_fields) 14 | return extractor.extract() 15 | 16 | def parse_default_extractor(self, response): 17 | return self.run_extractor(response, self.default_extractor['extractor_type'], exctactor_fields=self.default_extractor.get('fields', {})) 18 | 19 | def parse_other_extractors(self, response): 20 | data = {} 21 | for extractor_id, single_extractor_config in self.other_extractors.items(): 22 | # extractor_cls = getattr(importlib.import_module(f"crawlerflow.extractors"), single_extractor_config['extractor_type']) 23 | # extractor = extractor_cls(response, single_extractor_config['fields'] ) 24 | 25 | extractor = self.run_extractor( 26 | response, single_extractor_config['extractor_type'], exctactor_fields=single_extractor_config.get('fields', {})) 27 | data[extractor_id] = extractor.extract() 28 | return data 29 | -------------------------------------------------------------------------------- /src/crawlerflow/downloaders/requests_downloader.py: -------------------------------------------------------------------------------- 1 | from scrapy.http import HtmlResponse, Response, JsonRequest, TextResponse 2 | import requests 3 | from scrapy.downloadermiddlewares.httpcompression import ACCEPTED_ENCODINGS 4 | 5 | 6 | class RequestsDownloaderMiddleware(object): 7 | 8 | def process_request(self, request, spider): 9 | if spider.settings.getbool("COMPRESSION_ENABLED"): 10 | request.headers.setdefault("Accept-Encoding", b", ".join(ACCEPTED_ENCODINGS)) 11 | 12 | def process_request(self, request, spider): 13 | # only spiders with downloader = requests should trigger this. 14 | if not spider.downloader and spider.downloader != "requests": 15 | return 16 | headers = {x.decode(): request.headers[x].decode() for x in request.headers} 17 | kwargs = { "headers":headers, "cookies" :request.cookies} 18 | if spider.settings.getbool("COMPRESSION_ENABLED"): 19 | kwargs['stream'] = True 20 | res = requests.get(request.url, **kwargs) 21 | body=res.raw.read() if spider.settings.getbool("COMPRESSION_ENABLED") else res.content 22 | response = Response(request.url, 23 | body=body, 24 | headers=res.headers, 25 | status=res.status_code, 26 | request=request) 27 | return response -------------------------------------------------------------------------------- /src/crawlerflow/pipelines/mongodb.py: -------------------------------------------------------------------------------- 1 | import pymongo 2 | import logging 3 | 4 | 5 | class MongoDBPipeline(object): 6 | 7 | def __init__(self, mongo_uri, mongo_db, default_collection_name = None ): 8 | self.mongo_uri = mongo_uri 9 | self.mongo_db = mongo_db 10 | self.default_collection_name = default_collection_name 11 | self.client = None 12 | self.db = None 13 | 14 | 15 | @classmethod 16 | def from_crawler(cls, crawler): 17 | return cls( 18 | crawler.settings.get("STORAGE_MONGO_URI"), 19 | crawler.settings.get("STORAGE_MONGO_DATABASE"), 20 | default_collection_name = crawler.settings["STORAGE_MONGO_DEFAULT_COLLECTION"] 21 | ) 22 | 23 | def open_spider(self, spider): 24 | self.client = pymongo.MongoClient(self.mongo_uri) 25 | self.db = self.client[self.mongo_db] 26 | 27 | def close_spider(self, spider): 28 | self.client.close() 29 | 30 | @property 31 | def default_collection(self): 32 | return self.db[self.default_collection_name] 33 | 34 | def process_item(self, item, spider): 35 | meta__scraper_name = item.get("spider_meta__request", {}).get("spider_name") 36 | collection = self.db[meta__scraper_name] if meta__scraper_name else self.default_collection 37 | collection.insert_one(item) 38 | logging.debug("Item added to MongoDB database!") 39 | return item 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CrawlerFlow 2 | 3 | Web Crawlers orchestration framework that lets you create datasets from multiple web sources using yaml configurations. 4 | 5 | ## Features 6 | 7 | Features 8 | - [*] Write spiders in the YAML configs. 9 | - [*] Create extractors to scrape data using YAML configs (HTML, API, RSS) 10 | - [*] Define multiple extractors per spider. 11 | - [*] Use standard extractors to scrape data like Tables, Paragraphs, Meta tags, JSON+LD of the page. 12 | - [ ] Traverse between multiple websites. 13 | - [ ] Write Python Extractors for advanced extraction strategy 14 | 15 | 16 | 17 | ## Installation 18 | 19 | ``` 20 | pip install git+https://github.com/invana/crawlerflow#egg=crawlerflow 21 | ``` 22 | 23 | ## Usage 24 | 25 | ### Scraping with CrawlerFlow 26 | ```python 27 | from crawlerflow.runner import Crawlerflow 28 | from crawlerflow.utils import yaml_to_json 29 | 30 | 31 | crawl_requests = yaml_to_json(open("example-configs/crawlerflow/requests/github-detail-urls.yml")) 32 | spider_config = yaml_to_json(open("example-configs/crawlerflow/spiders/default-spider.yml")) 33 | github_default_extractor = yaml_to_json(open("example-configs/crawlerflow/extractors/github-blog-detail.yml")) 34 | 35 | flow = Crawlerflow() 36 | flow.add_spider_with_config(crawl_requests, spider_config, default_extractor=github_default_extractor) 37 | flow.start() 38 | ``` 39 | 40 | ### Scraping with WebCrawler 41 | 42 | ```python 43 | from crawlerflow.runner import WebCrawler 44 | from crawlerflow.utils import yaml_to_json 45 | 46 | 47 | scraper_config_files = [ 48 | "example-configs/webcrawler/APISpiders/api-publicapis-org.yml", 49 | "example-configs/webcrawler/HTMLSpiders/github-blog-list.yml", 50 | "example-configs/webcrawler/HTMLSpiders/github-blog-detail.yml" 51 | ] 52 | 53 | crawlerflow = WebCrawler() 54 | 55 | for scraper_config_file in scraper_config_files: 56 | scraper_config = yaml_to_json(open(scraper_config_file)) 57 | crawlerflow.add_spider_with_config(scraper_config) 58 | crawlerflow.start() 59 | ``` 60 | 61 | Refer `examples-configs/` folder for example configs. 62 | 63 | 64 | ## Available Extractors 65 | 66 | - [*] HTMLExtractor 67 | - [*] MetaTagExtractor 68 | - [*] JSONLDExtractor 69 | - [*] TableContentExtractor 70 | - [*] IconsExtractor 71 | 72 | -------------------------------------------------------------------------------- /src/crawlerflow/pipelines/default.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from scrapy.exceptions import DropItem 3 | from pymongo import MongoClient 4 | 5 | 6 | class WebScraperPipeline(object): 7 | 8 | @staticmethod 9 | def create_mongodb_connection(data_storage=None): 10 | db_client = MongoClient(data_storage.get("connection_uri")) 11 | db_conn = db_client[data_storage.get("database_name")] 12 | return db_conn 13 | 14 | def __init__(self, data_storages=None): 15 | 16 | self.data_storage_conns = {} 17 | for data_storage in data_storages: 18 | storage_type = data_storage.get("storage_type", "mongodb") 19 | storage_id = data_storage.get("storage_id", "default") 20 | if storage_type == "mongodb": 21 | self.data_storage_conns[storage_id] = { 22 | "_connection": self.create_mongodb_connection(data_storage=data_storage), 23 | "_data_storage": data_storage 24 | } 25 | 26 | else: 27 | raise NotImplementedError("yet to implement '{}' pipeline".format(storage_type)) 28 | 29 | @classmethod 30 | def from_crawler(cls, spider): 31 | return cls( 32 | data_storages=spider.spider.manifest.get("datasets") 33 | ) 34 | 35 | def process_item(self, item, spider): 36 | """ 37 | 38 | :param item: { 39 | "_data_storage_id": "default", 40 | "_data_storage_collection_name": "mycollection", 41 | "_data": 42 | } 43 | :param spider: 44 | :return: 45 | """ 46 | """ 47 | comes handy during the custom data saving. 48 | 49 | data_storage_id = item.get("_data_storage_id") 50 | 51 | """ 52 | data_storage_id = "default" 53 | data_storage = self.data_storage_conns.get(data_storage_id) 54 | data_storage_conn = data_storage.get("_connection") 55 | data_storage_collection_name = data_storage.get("_data_storage", {}).get("collection_name") 56 | 57 | data = item.get("_data") 58 | if None in [data_storage, data_storage_conn]: 59 | raise DropItem( 60 | "Data storage with id: {} doesn't exist, so dropping the item {}".format(data_storage_id, item)) 61 | 62 | data = dict(data) 63 | if "updated" not in data.keys(): 64 | data['updated'] = datetime.now() 65 | data_storage_type = data_storage.get("storage_type") or "mongodb" 66 | if data_storage_type == "mongodb": 67 | data_storage_conn[data_storage_collection_name].insert(data) 68 | else: 69 | raise NotImplementedError( 70 | "storage_type:{} not implemeneted, so extracted data is not saved.".format(data_storage_type)) 71 | 72 | return item 73 | -------------------------------------------------------------------------------- /src/crawlerflow/runner.py: -------------------------------------------------------------------------------- 1 | from scrapy.crawler import CrawlerProcess 2 | from scrapy.utils.project import get_project_settings 3 | from .settings import DEFAULT_SETTINGS_OVERRIDES 4 | from .exceptions import DefaultExtractorRequired 5 | from .utils import generate_uuid 6 | from slugify import slugify 7 | from .request import CrawlRequest 8 | import importlib 9 | import scrapy 10 | 11 | 12 | class WebScraperBase: 13 | 14 | 15 | def __init__(self, custom_settings=None, job_id=None) -> None: 16 | self.custom_settings = custom_settings or {} 17 | self.job_id = generate_uuid() if job_id is None else job_id 18 | self._process = None 19 | 20 | @property 21 | def settings(self): 22 | settings = dict(get_project_settings()) 23 | for k, v in DEFAULT_SETTINGS_OVERRIDES.items(): 24 | settings[k] = v 25 | for k, v in self.custom_settings.items(): 26 | settings[k] = v 27 | return settings 28 | 29 | @property 30 | def process(self): 31 | if self._process: 32 | return self._process 33 | self._process = CrawlerProcess(self.settings) 34 | return self._process 35 | 36 | def add_job_id(self, kwargs): 37 | kwargs['job_id'] = self.job_id 38 | return kwargs 39 | 40 | def add_spider(self, spider_cls, **kwargs): 41 | # https://doc.scrapy.org/en/latest/topics/spiders.html#spider-arguments 42 | self.process.crawl(spider_cls, **self.add_job_id(kwargs)) 43 | 44 | def _start(self): 45 | self.process.start() # the script will block here until all crawling jobs are finished 46 | 47 | def start(self): 48 | self._start() 49 | 50 | 51 | class WebCrawler(WebScraperBase): 52 | 53 | """ 54 | config_file = "example-configs/HTMLSpiders/github-blog-detail.yml" 55 | crawlerflow = WebCrawler() 56 | 57 | scraper_config = yaml.safe_load(open(config_file)) 58 | crawlerflow.add_spider_with_config(scraper_config) 59 | crawlerflow.start() 60 | """ 61 | 62 | def add_spider_with_config(self, spider_config): 63 | spider_cls = getattr(importlib.import_module( 64 | f"crawlerflow.spiders"), spider_config['spider_type']) 65 | self.process.crawl(spider_cls, **self.add_job_id(spider_config)) 66 | 67 | 68 | class Crawlerflow(WebScraperBase): 69 | """ 70 | flow = Crawlerflow() 71 | 72 | 73 | """ 74 | 75 | def add_spider_with_config(self, crawl_requests, spider_config, default_extractor=None): 76 | 77 | spider_config['name'] = slugify(spider_config['name']) 78 | if spider_config['spider_type'] == "HTMLSpider": 79 | if default_extractor is None: 80 | raise DefaultExtractorRequired() 81 | spider_config['default_extractor'] = default_extractor 82 | crawl_requests = [CrawlRequest(**req) for req in crawl_requests] 83 | 84 | spider_config['crawl_requests'] = crawl_requests 85 | spider_cls = getattr(importlib.import_module( 86 | f"crawlerflow.spiders"), spider_config['spider_type']) 87 | self.add_spider(spider_cls, **self.add_job_id(spider_config) ) -------------------------------------------------------------------------------- /src/crawlerflow/spiders/base.py: -------------------------------------------------------------------------------- 1 | import scrapy 2 | import datetime 3 | import abc 4 | from slugify import slugify 5 | from crawlerflow.utils import get_domain, get_urn, generate_uuid 6 | import requests 7 | import logging 8 | from ..request import CrawlRequest 9 | 10 | class SpiderBase(scrapy.Spider): 11 | 12 | name: str = "default-spider-name" 13 | start_urls: list = [] 14 | crawl_requests: list[CrawlRequest] = [] 15 | downloader: str = "default" 16 | spider_type: str = "Spider" 17 | custom_settings: dict = {} 18 | default_extractor: dict = None # this will be flattened 19 | other_extractors: dict = {} 20 | extra_data: dict = {} # this will added to all the scraped items 21 | callback_urls: list = [] 22 | job_id: str = lambda x: generate_uuid() 23 | 24 | def start_requests(self): 25 | if self.start_urls.__len__() > 0 and self.crawl_requests.__len__() > 0: 26 | raise Exception("Both start_urls and crawl_requests should not be set") 27 | elif self.start_urls.__len__() == 0 and self.crawl_requests.__len__() == 0: 28 | raise Exception("Both start_urls and crawl_requests cannot be None") 29 | elif self.start_urls.__len__() > 0: 30 | for start_url in self.start_urls: 31 | yield scrapy.Request(start_url, self.parse) 32 | else: 33 | for crawl_request in self.crawl_requests: 34 | yield scrapy.Request(crawl_request.url, self.parse, meta = crawl_request.meta) 35 | 36 | 37 | 38 | @property 39 | def spider_name(self): 40 | return slugify(self.name) 41 | 42 | def get_request_metadata(self, response): 43 | metadata = {} 44 | metadata['spider_meta__request'] = { 45 | "url": response.request.url, 46 | "domain": get_domain(response.request.url), 47 | "urn": get_urn(response.request.url), 48 | "spider_name": self.spider_name, 49 | "job_id": self.job_id, 50 | "meta": response.request.meta 51 | } 52 | metadata['spider_meta__response'] = { 53 | "scraped_at": datetime.datetime.now() 54 | } 55 | metadata['spider_meta__extra_data'] = self.extra_data 56 | return metadata 57 | 58 | @abc.abstractclassmethod 59 | def parse_default_extractor(self, response): 60 | pass 61 | 62 | @abc.abstractclassmethod 63 | def parse_other_extractors(self, response): 64 | pass 65 | 66 | def parse(self, response): 67 | metadata = self.get_request_metadata(response) 68 | data = self.parse_default_extractor(response) 69 | other_extractors_data = self.parse_other_extractors(response) 70 | if other_extractors_data: 71 | for k, v in other_extractors_data.items(): 72 | data[f'other_extractors__{k}'] = v 73 | data.update(metadata) 74 | yield data 75 | 76 | def closed(self, reason): 77 | if self.callback_urls: 78 | for callback_url in self.callback_urls: 79 | self.log(f"Triggering callback {callback_url}") 80 | res = requests.get(callback_url) 81 | self.log(f"Triggered callback {callback_url}. response status_code is {res.status_code}", 82 | level=logging.INFO, 83 | # extra= {"spider":self.spider_name} 84 | ) 85 | -------------------------------------------------------------------------------- /src/crawlerflow/extractors/content.py: -------------------------------------------------------------------------------- 1 | from .base import ExtractorBase 2 | import json 3 | 4 | 5 | class HTMLExtractor(ExtractorBase): 6 | 7 | def extract(self): 8 | return self.extract_fields(self.extractor_fields) 9 | 10 | 11 | class MetaTagExtractor(ExtractorBase): 12 | 13 | def extract(self): 14 | # TODO - make this extractor with YAML later 15 | data = {} 16 | elements = self.get_elem_by_css(self.html, "meta") 17 | for element in elements: 18 | # for open graph type of meta tags 19 | meta_property = element.xpath( 20 | "@{0}".format('property')).extract_first() 21 | meta_content = element.xpath( 22 | "@{0}".format('content')).extract_first() 23 | meta_name = element.xpath("@{0}".format('name')).extract_first() 24 | if meta_property: 25 | data[meta_property] = meta_content 26 | if meta_name: 27 | data[meta_name] = meta_content 28 | data["shortlink_url"] = self.html.xpath( 29 | '//link[@rel="shortlink"]').xpath("@href").extract_first() 30 | data["canonical_url"] = self.html.xpath( 31 | '//link[@rel="canonical"]').xpath("@href").extract_first() 32 | 33 | data = dict(sorted(data.items(), key=lambda x: x[0])) 34 | 35 | data["title"] = self.get_value_by_css(self.html, 'title::text') 36 | return data 37 | 38 | 39 | class TableContentExtractor(ExtractorBase): 40 | 41 | def extract(self): 42 | 43 | table_el = self.get_elem_by_css(self.html, "table") 44 | table_data = [] 45 | if table_el is None: 46 | return table_data 47 | table_headers = [th.extract() 48 | for th in table_el.css("thead tr th::text")] 49 | for row in table_el.css("tbody tr"): 50 | row_data = [td.extract() for td in row.css("td::text")] 51 | row_dict = dict(zip(table_headers, row_data)) 52 | table_data.append(row_dict) 53 | return table_data 54 | 55 | 56 | class JSONLDExtractor(ExtractorBase): 57 | 58 | def extract(self): 59 | extracted_data = [] 60 | elements = self.html.xpath( 61 | '//script[@type="application/ld+json"]/text()').extract() 62 | for element in elements: 63 | # for open graph type of meta tags 64 | try: 65 | element = element.strip() 66 | element = json.loads(element) 67 | extracted_data.append(element) 68 | except Exception as e: 69 | pass 70 | 71 | return extracted_data 72 | 73 | 74 | class IconsExtractor(ExtractorBase): 75 | 76 | def extract(self): 77 | meta_data_dict = {} 78 | 79 | favicon = self.html.xpath( 80 | '//link[@rel="shortcut icon"]').xpath("@href").get() 81 | if favicon: 82 | meta_data_dict['favicon'] = favicon 83 | 84 | elements = self.html.xpath( 85 | '//link[@rel="icon" or @rel="apple-touch-icon-precomposed"]') 86 | for element in elements: 87 | # for open graph type of meta tags 88 | meta_property = element.xpath("@sizes").extract_first() 89 | if meta_property: 90 | meta_property = meta_property.replace( 91 | ":", "__").replace(".", "__") 92 | meta_data_dict[meta_property] = element.xpath( 93 | "@{0}".format('href')).extract_first() 94 | return meta_data_dict 95 | 96 | 97 | class FeedUrlsExtractor(ExtractorBase): 98 | 99 | def extract(self): 100 | data = {} 101 | 102 | data['rss__xml'] = self.html.xpath('//link[@type="application/rss+xml"]').xpath( 103 | "@href").extract() 104 | data['rss__atom'] = self.html.xpath('//link[@type="application/atom+xml"]').xpath( 105 | "@href").extract() 106 | return data 107 | 108 | 109 | class ImagesExtractor(ExtractorBase): 110 | 111 | def extract(self): 112 | image_urls = [] 113 | images_selector = self.html.xpath('//img/@src').extract() 114 | for selector in images_selector: 115 | image_urls.append(selector) 116 | return image_urls 117 | -------------------------------------------------------------------------------- /src/crawlerflow/extractors/base.py: -------------------------------------------------------------------------------- 1 | from parsel import Selector 2 | from scrapy.http.response.html import HtmlResponse 3 | import importlib 4 | 5 | 6 | class ExtractorBase: 7 | 8 | DATA_TYPES = { 9 | "StringField": str, 10 | "IntField": int, 11 | "FloatField": float, 12 | "DictField": dict 13 | } 14 | 15 | DEFAULT_DATA_TYPE = "StringField" 16 | 17 | def __init__(self, html, extractor_fields=None): 18 | """ 19 | :param html: html html of the request 20 | :param extractor_fields: extractor configuration in json; this is optional in most cases. 21 | """ 22 | self.html = html if isinstance(html, Selector) or isinstance(html, HtmlResponse) else Selector(text=html) 23 | self.extractor_fields = extractor_fields or {} 24 | 25 | 26 | def get_elem_by_css(self, html_element, selector_string ): 27 | return html_element.css(selector_string) 28 | 29 | def get_value_by_css(self, html_element, selector_string, fetch_multiple_values=False): 30 | try: 31 | el = self.get_elem_by_css(html_element, selector_string ) 32 | # el = el.xpath(f"@{attribute}") if attribute else el 33 | return el.get() if fetch_multiple_values is False else el.getall() 34 | except Exception as e: 35 | return None 36 | def get_data_type(self, type_string_raw): 37 | # type_string ; 38 | # examples usages: StringField; for list of StringField: [StringField] 39 | is_multiple = True if type(type_string_raw) is list else False 40 | type_string = type_string_raw[0] if is_multiple is True else type_string_raw 41 | return type_string, is_multiple 42 | 43 | def convert_to_data_type(self, data, type_string=None, is_multiple=True): 44 | data_type_cls = self.DATA_TYPES[type_string] 45 | if type_string in ["IntField", "FloatField"]: 46 | data = data.replace(',','') 47 | if data: 48 | return data_type_cls(data) if is_multiple is False else [ data_type_cls(d) for d in data] 49 | return 50 | 51 | def extract_from_single_element(self, fields, html_element=None): 52 | data = {} 53 | html = html_element if html_element else self.html 54 | for field_name, field_config in fields.items(): 55 | css_selector = field_config.get("selector") 56 | extractor_type = field_config.get("extractor_type") 57 | 58 | 59 | if css_selector is None and extractor_type is None: 60 | raise Exception("Field extractor should have `selector` or `extractor_type` type specified") 61 | 62 | 63 | elif css_selector: 64 | type_string, is_multiple = self.get_data_type(field_config.get('type', self.DEFAULT_DATA_TYPE)) 65 | if type_string == "DictField": 66 | nested_fields = field_config.get("fields", {}) 67 | child_html_element = self.get_elem_by_css(html, css_selector) 68 | data[field_name] = self.extract_from_multiple_element(nested_fields, child_html_element) if is_multiple is True \ 69 | else self.extract_from_single_element(nested_fields, html_element=child_html_element) 70 | else: 71 | extracted_data = self.get_value_by_css(html, css_selector, fetch_multiple_values=is_multiple) 72 | data[field_name] = self.convert_to_data_type(extracted_data, type_string=type_string, is_multiple=is_multiple) 73 | elif extractor_type: 74 | extractor_cls = getattr(importlib.import_module(f"crawlerflow.extractors"), extractor_type) 75 | extractor = extractor_cls(html, extractor_fields=field_config.get("fields")) 76 | data[field_name] = extractor.extract() 77 | return data 78 | 79 | def extract_from_multiple_element(self, fields, html_elements): 80 | data = [] 81 | for html_element in html_elements: 82 | datum = self.extract_from_single_element(fields, html_element=html_element) 83 | data.append(datum) 84 | return data 85 | 86 | 87 | def extract_fields(self, fields:dict, html_element=None): 88 | return self.extract_from_single_element(fields, html_element=html_element) 89 | 90 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "04fec7256a656cccad2be8510ccd4348d2c28d706c3535560041cf120d1d40e5" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.11" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "attrs": { 20 | "hashes": [ 21 | "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04", 22 | "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015" 23 | ], 24 | "markers": "python_version >= '3.7'", 25 | "version": "==23.1.0" 26 | }, 27 | "automat": { 28 | "hashes": [ 29 | "sha256:c3164f8742b9dc440f3682482d32aaff7bb53f71740dd018533f9de286b64180", 30 | "sha256:e56beb84edad19dcc11d30e8d9b895f75deeb5ef5e96b84a467066b3b84bb04e" 31 | ], 32 | "version": "==22.10.0" 33 | }, 34 | "certifi": { 35 | "hashes": [ 36 | "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082", 37 | "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9" 38 | ], 39 | "markers": "python_version >= '3.6'", 40 | "version": "==2023.7.22" 41 | }, 42 | "cffi": { 43 | "hashes": [ 44 | "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc", 45 | "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a", 46 | "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417", 47 | "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab", 48 | "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520", 49 | "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36", 50 | "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743", 51 | "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8", 52 | "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed", 53 | "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684", 54 | "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56", 55 | "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324", 56 | "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d", 57 | "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235", 58 | "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e", 59 | "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088", 60 | "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000", 61 | "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7", 62 | "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e", 63 | "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673", 64 | "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c", 65 | "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe", 66 | "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2", 67 | "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098", 68 | "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8", 69 | "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a", 70 | "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0", 71 | "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b", 72 | "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896", 73 | "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e", 74 | "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9", 75 | "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2", 76 | "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b", 77 | "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6", 78 | "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404", 79 | "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f", 80 | "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0", 81 | "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4", 82 | "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc", 83 | "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936", 84 | "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba", 85 | "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872", 86 | "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb", 87 | "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614", 88 | "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1", 89 | "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d", 90 | "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969", 91 | "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b", 92 | "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4", 93 | "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627", 94 | "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956", 95 | "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357" 96 | ], 97 | "markers": "python_version >= '3.8'", 98 | "version": "==1.16.0" 99 | }, 100 | "charset-normalizer": { 101 | "hashes": [ 102 | "sha256:02673e456dc5ab13659f85196c534dc596d4ef260e4d86e856c3b2773ce09843", 103 | "sha256:02af06682e3590ab952599fbadac535ede5d60d78848e555aa58d0c0abbde786", 104 | "sha256:03680bb39035fbcffe828eae9c3f8afc0428c91d38e7d61aa992ef7a59fb120e", 105 | "sha256:0570d21da019941634a531444364f2482e8db0b3425fcd5ac0c36565a64142c8", 106 | "sha256:09c77f964f351a7369cc343911e0df63e762e42bac24cd7d18525961c81754f4", 107 | "sha256:0d3d5b7db9ed8a2b11a774db2bbea7ba1884430a205dbd54a32d61d7c2a190fa", 108 | "sha256:1063da2c85b95f2d1a430f1c33b55c9c17ffaf5e612e10aeaad641c55a9e2b9d", 109 | "sha256:12ebea541c44fdc88ccb794a13fe861cc5e35d64ed689513a5c03d05b53b7c82", 110 | "sha256:153e7b6e724761741e0974fc4dcd406d35ba70b92bfe3fedcb497226c93b9da7", 111 | "sha256:15b26ddf78d57f1d143bdf32e820fd8935d36abe8a25eb9ec0b5a71c82eb3895", 112 | "sha256:1872d01ac8c618a8da634e232f24793883d6e456a66593135aeafe3784b0848d", 113 | "sha256:187d18082694a29005ba2944c882344b6748d5be69e3a89bf3cc9d878e548d5a", 114 | "sha256:1b2919306936ac6efb3aed1fbf81039f7087ddadb3160882a57ee2ff74fd2382", 115 | "sha256:232ac332403e37e4a03d209a3f92ed9071f7d3dbda70e2a5e9cff1c4ba9f0678", 116 | "sha256:23e8565ab7ff33218530bc817922fae827420f143479b753104ab801145b1d5b", 117 | "sha256:24817cb02cbef7cd499f7c9a2735286b4782bd47a5b3516a0e84c50eab44b98e", 118 | "sha256:249c6470a2b60935bafd1d1d13cd613f8cd8388d53461c67397ee6a0f5dce741", 119 | "sha256:24a91a981f185721542a0b7c92e9054b7ab4fea0508a795846bc5b0abf8118d4", 120 | "sha256:2502dd2a736c879c0f0d3e2161e74d9907231e25d35794584b1ca5284e43f596", 121 | "sha256:250c9eb0f4600361dd80d46112213dff2286231d92d3e52af1e5a6083d10cad9", 122 | "sha256:278c296c6f96fa686d74eb449ea1697f3c03dc28b75f873b65b5201806346a69", 123 | "sha256:2935ffc78db9645cb2086c2f8f4cfd23d9b73cc0dc80334bc30aac6f03f68f8c", 124 | "sha256:2f4a0033ce9a76e391542c182f0d48d084855b5fcba5010f707c8e8c34663d77", 125 | "sha256:30a85aed0b864ac88309b7d94be09f6046c834ef60762a8833b660139cfbad13", 126 | "sha256:380c4bde80bce25c6e4f77b19386f5ec9db230df9f2f2ac1e5ad7af2caa70459", 127 | "sha256:3ae38d325b512f63f8da31f826e6cb6c367336f95e418137286ba362925c877e", 128 | "sha256:3b447982ad46348c02cb90d230b75ac34e9886273df3a93eec0539308a6296d7", 129 | "sha256:3debd1150027933210c2fc321527c2299118aa929c2f5a0a80ab6953e3bd1908", 130 | "sha256:4162918ef3098851fcd8a628bf9b6a98d10c380725df9e04caf5ca6dd48c847a", 131 | "sha256:468d2a840567b13a590e67dd276c570f8de00ed767ecc611994c301d0f8c014f", 132 | "sha256:4cc152c5dd831641e995764f9f0b6589519f6f5123258ccaca8c6d34572fefa8", 133 | "sha256:542da1178c1c6af8873e143910e2269add130a299c9106eef2594e15dae5e482", 134 | "sha256:557b21a44ceac6c6b9773bc65aa1b4cc3e248a5ad2f5b914b91579a32e22204d", 135 | "sha256:5707a746c6083a3a74b46b3a631d78d129edab06195a92a8ece755aac25a3f3d", 136 | "sha256:588245972aca710b5b68802c8cad9edaa98589b1b42ad2b53accd6910dad3545", 137 | "sha256:5adf257bd58c1b8632046bbe43ee38c04e1038e9d37de9c57a94d6bd6ce5da34", 138 | "sha256:619d1c96099be5823db34fe89e2582b336b5b074a7f47f819d6b3a57ff7bdb86", 139 | "sha256:63563193aec44bce707e0c5ca64ff69fa72ed7cf34ce6e11d5127555756fd2f6", 140 | "sha256:67b8cc9574bb518ec76dc8e705d4c39ae78bb96237cb533edac149352c1f39fe", 141 | "sha256:6a685067d05e46641d5d1623d7c7fdf15a357546cbb2f71b0ebde91b175ffc3e", 142 | "sha256:70f1d09c0d7748b73290b29219e854b3207aea922f839437870d8cc2168e31cc", 143 | "sha256:750b446b2ffce1739e8578576092179160f6d26bd5e23eb1789c4d64d5af7dc7", 144 | "sha256:7966951325782121e67c81299a031f4c115615e68046f79b85856b86ebffc4cd", 145 | "sha256:7b8b8bf1189b3ba9b8de5c8db4d541b406611a71a955bbbd7385bbc45fcb786c", 146 | "sha256:7f5d10bae5d78e4551b7be7a9b29643a95aded9d0f602aa2ba584f0388e7a557", 147 | "sha256:805dfea4ca10411a5296bcc75638017215a93ffb584c9e344731eef0dcfb026a", 148 | "sha256:81bf654678e575403736b85ba3a7867e31c2c30a69bc57fe88e3ace52fb17b89", 149 | "sha256:82eb849f085624f6a607538ee7b83a6d8126df6d2f7d3b319cb837b289123078", 150 | "sha256:85a32721ddde63c9df9ebb0d2045b9691d9750cb139c161c80e500d210f5e26e", 151 | "sha256:86d1f65ac145e2c9ed71d8ffb1905e9bba3a91ae29ba55b4c46ae6fc31d7c0d4", 152 | "sha256:86f63face3a527284f7bb8a9d4f78988e3c06823f7bea2bd6f0e0e9298ca0403", 153 | "sha256:8eaf82f0eccd1505cf39a45a6bd0a8cf1c70dcfc30dba338207a969d91b965c0", 154 | "sha256:93aa7eef6ee71c629b51ef873991d6911b906d7312c6e8e99790c0f33c576f89", 155 | "sha256:96c2b49eb6a72c0e4991d62406e365d87067ca14c1a729a870d22354e6f68115", 156 | "sha256:9cf3126b85822c4e53aa28c7ec9869b924d6fcfb76e77a45c44b83d91afd74f9", 157 | "sha256:9fe359b2e3a7729010060fbca442ca225280c16e923b37db0e955ac2a2b72a05", 158 | "sha256:a0ac5e7015a5920cfce654c06618ec40c33e12801711da6b4258af59a8eff00a", 159 | "sha256:a3f93dab657839dfa61025056606600a11d0b696d79386f974e459a3fbc568ec", 160 | "sha256:a4b71f4d1765639372a3b32d2638197f5cd5221b19531f9245fcc9ee62d38f56", 161 | "sha256:aae32c93e0f64469f74ccc730a7cb21c7610af3a775157e50bbd38f816536b38", 162 | "sha256:aaf7b34c5bc56b38c931a54f7952f1ff0ae77a2e82496583b247f7c969eb1479", 163 | "sha256:abecce40dfebbfa6abf8e324e1860092eeca6f7375c8c4e655a8afb61af58f2c", 164 | "sha256:abf0d9f45ea5fb95051c8bfe43cb40cda383772f7e5023a83cc481ca2604d74e", 165 | "sha256:ac71b2977fb90c35d41c9453116e283fac47bb9096ad917b8819ca8b943abecd", 166 | "sha256:ada214c6fa40f8d800e575de6b91a40d0548139e5dc457d2ebb61470abf50186", 167 | "sha256:b09719a17a2301178fac4470d54b1680b18a5048b481cb8890e1ef820cb80455", 168 | "sha256:b1121de0e9d6e6ca08289583d7491e7fcb18a439305b34a30b20d8215922d43c", 169 | "sha256:b3b2316b25644b23b54a6f6401074cebcecd1244c0b8e80111c9a3f1c8e83d65", 170 | "sha256:b3d9b48ee6e3967b7901c052b670c7dda6deb812c309439adaffdec55c6d7b78", 171 | "sha256:b5bcf60a228acae568e9911f410f9d9e0d43197d030ae5799e20dca8df588287", 172 | "sha256:b8f3307af845803fb0b060ab76cf6dd3a13adc15b6b451f54281d25911eb92df", 173 | "sha256:c2af80fb58f0f24b3f3adcb9148e6203fa67dd3f61c4af146ecad033024dde43", 174 | "sha256:c350354efb159b8767a6244c166f66e67506e06c8924ed74669b2c70bc8735b1", 175 | "sha256:c5a74c359b2d47d26cdbbc7845e9662d6b08a1e915eb015d044729e92e7050b7", 176 | "sha256:c71f16da1ed8949774ef79f4a0260d28b83b3a50c6576f8f4f0288d109777989", 177 | "sha256:d47ecf253780c90ee181d4d871cd655a789da937454045b17b5798da9393901a", 178 | "sha256:d7eff0f27edc5afa9e405f7165f85a6d782d308f3b6b9d96016c010597958e63", 179 | "sha256:d97d85fa63f315a8bdaba2af9a6a686e0eceab77b3089af45133252618e70884", 180 | "sha256:db756e48f9c5c607b5e33dd36b1d5872d0422e960145b08ab0ec7fd420e9d649", 181 | "sha256:dc45229747b67ffc441b3de2f3ae5e62877a282ea828a5bdb67883c4ee4a8810", 182 | "sha256:e0fc42822278451bc13a2e8626cf2218ba570f27856b536e00cfa53099724828", 183 | "sha256:e39c7eb31e3f5b1f88caff88bcff1b7f8334975b46f6ac6e9fc725d829bc35d4", 184 | "sha256:e46cd37076971c1040fc8c41273a8b3e2c624ce4f2be3f5dfcb7a430c1d3acc2", 185 | "sha256:e5c1502d4ace69a179305abb3f0bb6141cbe4714bc9b31d427329a95acfc8bdd", 186 | "sha256:edfe077ab09442d4ef3c52cb1f9dab89bff02f4524afc0acf2d46be17dc479f5", 187 | "sha256:effe5406c9bd748a871dbcaf3ac69167c38d72db8c9baf3ff954c344f31c4cbe", 188 | "sha256:f0d1e3732768fecb052d90d62b220af62ead5748ac51ef61e7b32c266cac9293", 189 | "sha256:f5969baeaea61c97efa706b9b107dcba02784b1601c74ac84f2a532ea079403e", 190 | "sha256:f8888e31e3a85943743f8fc15e71536bda1c81d5aa36d014a3c0c44481d7db6e", 191 | "sha256:fc52b79d83a3fe3a360902d3f5d79073a993597d48114c29485e9431092905d8" 192 | ], 193 | "markers": "python_full_version >= '3.7.0'", 194 | "version": "==3.3.0" 195 | }, 196 | "constantly": { 197 | "hashes": [ 198 | "sha256:586372eb92059873e29eba4f9dec8381541b4d3834660707faf8ba59146dfc35", 199 | "sha256:dd2fa9d6b1a51a83f0d7dd76293d734046aa176e384bf6e33b7e44880eb37c5d" 200 | ], 201 | "version": "==15.1.0" 202 | }, 203 | "cryptography": { 204 | "hashes": [ 205 | "sha256:004b6ccc95943f6a9ad3142cfabcc769d7ee38a3f60fb0dddbfb431f818c3a67", 206 | "sha256:047c4603aeb4bbd8db2756e38f5b8bd7e94318c047cfe4efeb5d715e08b49311", 207 | "sha256:0d9409894f495d465fe6fda92cb70e8323e9648af912d5b9141d616df40a87b8", 208 | "sha256:23a25c09dfd0d9f28da2352503b23e086f8e78096b9fd585d1d14eca01613e13", 209 | "sha256:2ed09183922d66c4ec5fdaa59b4d14e105c084dd0febd27452de8f6f74704143", 210 | "sha256:35c00f637cd0b9d5b6c6bd11b6c3359194a8eba9c46d4e875a3660e3b400005f", 211 | "sha256:37480760ae08065437e6573d14be973112c9e6dcaf5f11d00147ee74f37a3829", 212 | "sha256:3b224890962a2d7b57cf5eeb16ccaafba6083f7b811829f00476309bce2fe0fd", 213 | "sha256:5a0f09cefded00e648a127048119f77bc2b2ec61e736660b5789e638f43cc397", 214 | "sha256:5b72205a360f3b6176485a333256b9bcd48700fc755fef51c8e7e67c4b63e3ac", 215 | "sha256:7e53db173370dea832190870e975a1e09c86a879b613948f09eb49324218c14d", 216 | "sha256:7febc3094125fc126a7f6fb1f420d0da639f3f32cb15c8ff0dc3997c4549f51a", 217 | "sha256:80907d3faa55dc5434a16579952ac6da800935cd98d14dbd62f6f042c7f5e839", 218 | "sha256:86defa8d248c3fa029da68ce61fe735432b047e32179883bdb1e79ed9bb8195e", 219 | "sha256:8ac4f9ead4bbd0bc8ab2d318f97d85147167a488be0e08814a37eb2f439d5cf6", 220 | "sha256:93530900d14c37a46ce3d6c9e6fd35dbe5f5601bf6b3a5c325c7bffc030344d9", 221 | "sha256:9eeb77214afae972a00dee47382d2591abe77bdae166bda672fb1e24702a3860", 222 | "sha256:b5f4dfe950ff0479f1f00eda09c18798d4f49b98f4e2006d644b3301682ebdca", 223 | "sha256:c3391bd8e6de35f6f1140e50aaeb3e2b3d6a9012536ca23ab0d9c35ec18c8a91", 224 | "sha256:c880eba5175f4307129784eca96f4e70b88e57aa3f680aeba3bab0e980b0f37d", 225 | "sha256:cecfefa17042941f94ab54f769c8ce0fe14beff2694e9ac684176a2535bf9714", 226 | "sha256:e40211b4923ba5a6dc9769eab704bdb3fbb58d56c5b336d30996c24fcf12aadb", 227 | "sha256:efc8ad4e6fc4f1752ebfb58aefece8b4e3c4cae940b0994d43649bdfce8d0d4f" 228 | ], 229 | "markers": "python_version >= '3.7'", 230 | "version": "==41.0.4" 231 | }, 232 | "cssselect": { 233 | "hashes": [ 234 | "sha256:666b19839cfaddb9ce9d36bfe4c969132c647b92fc9088c4e23f786b30f1b3dc", 235 | "sha256:da1885f0c10b60c03ed5eccbb6b68d6eff248d91976fcde348f395d54c9fd35e" 236 | ], 237 | "markers": "python_version >= '3.7'", 238 | "version": "==1.2.0" 239 | }, 240 | "dnspython": { 241 | "hashes": [ 242 | "sha256:57c6fbaaeaaf39c891292012060beb141791735dbb4004798328fc2c467402d8", 243 | "sha256:8dcfae8c7460a2f84b4072e26f1c9f4101ca20c071649cb7c34e8b6a93d58984" 244 | ], 245 | "markers": "python_version >= '3.8' and python_version < '4.0'", 246 | "version": "==2.4.2" 247 | }, 248 | "feedparser": { 249 | "hashes": [ 250 | "sha256:27da485f4637ce7163cdeab13a80312b93b7d0c1b775bef4a47629a3110bca51", 251 | "sha256:79c257d526d13b944e965f6095700587f27388e50ea16fd245babe4dfae7024f" 252 | ], 253 | "index": "pypi", 254 | "markers": "python_version >= '3.6'", 255 | "version": "==6.0.10" 256 | }, 257 | "filelock": { 258 | "hashes": [ 259 | "sha256:08c21d87ded6e2b9da6728c3dff51baf1dcecf973b768ef35bcbc3447edb9ad4", 260 | "sha256:2e6f249f1f3654291606e046b09f1fd5eac39b360664c27f5aad072012f8bcbd" 261 | ], 262 | "markers": "python_version >= '3.8'", 263 | "version": "==3.12.4" 264 | }, 265 | "hyperlink": { 266 | "hashes": [ 267 | "sha256:427af957daa58bc909471c6c40f74c5450fa123dd093fc53efd2e91d2705a56b", 268 | "sha256:e6b14c37ecb73e89c77d78cdb4c2cc8f3fb59a885c5b3f819ff4ed80f25af1b4" 269 | ], 270 | "version": "==21.0.0" 271 | }, 272 | "idna": { 273 | "hashes": [ 274 | "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", 275 | "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" 276 | ], 277 | "markers": "python_version >= '3.5'", 278 | "version": "==3.4" 279 | }, 280 | "incremental": { 281 | "hashes": [ 282 | "sha256:912feeb5e0f7e0188e6f42241d2f450002e11bbc0937c65865045854c24c0bd0", 283 | "sha256:b864a1f30885ee72c5ac2835a761b8fe8aa9c28b9395cacf27286602688d3e51" 284 | ], 285 | "version": "==22.10.0" 286 | }, 287 | "itemadapter": { 288 | "hashes": [ 289 | "sha256:2ac1fbcc363b789a18639935ca322e50a65a0a7dfdd8d973c34e2c468e6c0f94", 290 | "sha256:77758485fb0ac10730d4b131363e37d65cb8db2450bfec7a57c3f3271f4a48a9" 291 | ], 292 | "markers": "python_version >= '3.7'", 293 | "version": "==0.8.0" 294 | }, 295 | "itemloaders": { 296 | "hashes": [ 297 | "sha256:21d81c61da6a08b48e5996288cdf3031c0f92e5d0075920a0242527523e14a48", 298 | "sha256:c8c82fe0c11fc4cdd08ec04df0b3c43f3cb7190002edb517e02d55de8efc2aeb" 299 | ], 300 | "markers": "python_version >= '3.7'", 301 | "version": "==1.1.0" 302 | }, 303 | "jmespath": { 304 | "hashes": [ 305 | "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", 306 | "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe" 307 | ], 308 | "markers": "python_version >= '3.7'", 309 | "version": "==1.0.1" 310 | }, 311 | "lxml": { 312 | "hashes": [ 313 | "sha256:05186a0f1346ae12553d66df1cfce6f251589fea3ad3da4f3ef4e34b2d58c6a3", 314 | "sha256:075b731ddd9e7f68ad24c635374211376aa05a281673ede86cbe1d1b3455279d", 315 | "sha256:081d32421db5df44c41b7f08a334a090a545c54ba977e47fd7cc2deece78809a", 316 | "sha256:0a3d3487f07c1d7f150894c238299934a2a074ef590b583103a45002035be120", 317 | "sha256:0bfd0767c5c1de2551a120673b72e5d4b628737cb05414f03c3277bf9bed3305", 318 | "sha256:0c0850c8b02c298d3c7006b23e98249515ac57430e16a166873fc47a5d549287", 319 | "sha256:0e2cb47860da1f7e9a5256254b74ae331687b9672dfa780eed355c4c9c3dbd23", 320 | "sha256:120fa9349a24c7043854c53cae8cec227e1f79195a7493e09e0c12e29f918e52", 321 | "sha256:1247694b26342a7bf47c02e513d32225ededd18045264d40758abeb3c838a51f", 322 | "sha256:141f1d1a9b663c679dc524af3ea1773e618907e96075262726c7612c02b149a4", 323 | "sha256:14e019fd83b831b2e61baed40cab76222139926b1fb5ed0e79225bc0cae14584", 324 | "sha256:1509dd12b773c02acd154582088820893109f6ca27ef7291b003d0e81666109f", 325 | "sha256:17a753023436a18e27dd7769e798ce302963c236bc4114ceee5b25c18c52c693", 326 | "sha256:1e224d5755dba2f4a9498e150c43792392ac9b5380aa1b845f98a1618c94eeef", 327 | "sha256:1f447ea5429b54f9582d4b955f5f1985f278ce5cf169f72eea8afd9502973dd5", 328 | "sha256:23eed6d7b1a3336ad92d8e39d4bfe09073c31bfe502f20ca5116b2a334f8ec02", 329 | "sha256:25f32acefac14ef7bd53e4218fe93b804ef6f6b92ffdb4322bb6d49d94cad2bc", 330 | "sha256:2c74524e179f2ad6d2a4f7caf70e2d96639c0954c943ad601a9e146c76408ed7", 331 | "sha256:303bf1edce6ced16bf67a18a1cf8339d0db79577eec5d9a6d4a80f0fb10aa2da", 332 | "sha256:3331bece23c9ee066e0fb3f96c61322b9e0f54d775fccefff4c38ca488de283a", 333 | "sha256:3e9bdd30efde2b9ccfa9cb5768ba04fe71b018a25ea093379c857c9dad262c40", 334 | "sha256:411007c0d88188d9f621b11d252cce90c4a2d1a49db6c068e3c16422f306eab8", 335 | "sha256:42871176e7896d5d45138f6d28751053c711ed4d48d8e30b498da155af39aebd", 336 | "sha256:46f409a2d60f634fe550f7133ed30ad5321ae2e6630f13657fb9479506b00601", 337 | "sha256:48628bd53a426c9eb9bc066a923acaa0878d1e86129fd5359aee99285f4eed9c", 338 | "sha256:48d6ed886b343d11493129e019da91d4039826794a3e3027321c56d9e71505be", 339 | "sha256:4930be26af26ac545c3dffb662521d4e6268352866956672231887d18f0eaab2", 340 | "sha256:4aec80cde9197340bc353d2768e2a75f5f60bacda2bab72ab1dc499589b3878c", 341 | "sha256:4c28a9144688aef80d6ea666c809b4b0e50010a2aca784c97f5e6bf143d9f129", 342 | "sha256:4d2d1edbca80b510443f51afd8496be95529db04a509bc8faee49c7b0fb6d2cc", 343 | "sha256:4dd9a263e845a72eacb60d12401e37c616438ea2e5442885f65082c276dfb2b2", 344 | "sha256:4f1026bc732b6a7f96369f7bfe1a4f2290fb34dce00d8644bc3036fb351a4ca1", 345 | "sha256:4fb960a632a49f2f089d522f70496640fdf1218f1243889da3822e0a9f5f3ba7", 346 | "sha256:50670615eaf97227d5dc60de2dc99fb134a7130d310d783314e7724bf163f75d", 347 | "sha256:50baa9c1c47efcaef189f31e3d00d697c6d4afda5c3cde0302d063492ff9b477", 348 | "sha256:53ace1c1fd5a74ef662f844a0413446c0629d151055340e9893da958a374f70d", 349 | "sha256:5515edd2a6d1a5a70bfcdee23b42ec33425e405c5b351478ab7dc9347228f96e", 350 | "sha256:56dc1f1ebccc656d1b3ed288f11e27172a01503fc016bcabdcbc0978b19352b7", 351 | "sha256:578695735c5a3f51569810dfebd05dd6f888147a34f0f98d4bb27e92b76e05c2", 352 | "sha256:57aba1bbdf450b726d58b2aea5fe47c7875f5afb2c4a23784ed78f19a0462574", 353 | "sha256:57d6ba0ca2b0c462f339640d22882acc711de224d769edf29962b09f77129cbf", 354 | "sha256:5c245b783db29c4e4fbbbfc9c5a78be496c9fea25517f90606aa1f6b2b3d5f7b", 355 | "sha256:5c31c7462abdf8f2ac0577d9f05279727e698f97ecbb02f17939ea99ae8daa98", 356 | "sha256:64f479d719dc9f4c813ad9bb6b28f8390360660b73b2e4beb4cb0ae7104f1c12", 357 | "sha256:65299ea57d82fb91c7f019300d24050c4ddeb7c5a190e076b5f48a2b43d19c42", 358 | "sha256:6689a3d7fd13dc687e9102a27e98ef33730ac4fe37795d5036d18b4d527abd35", 359 | "sha256:690dafd0b187ed38583a648076865d8c229661ed20e48f2335d68e2cf7dc829d", 360 | "sha256:6fc3c450eaa0b56f815c7b62f2b7fba7266c4779adcf1cece9e6deb1de7305ce", 361 | "sha256:704f61ba8c1283c71b16135caf697557f5ecf3e74d9e453233e4771d68a1f42d", 362 | "sha256:71c52db65e4b56b8ddc5bb89fb2e66c558ed9d1a74a45ceb7dcb20c191c3df2f", 363 | "sha256:71d66ee82e7417828af6ecd7db817913cb0cf9d4e61aa0ac1fde0583d84358db", 364 | "sha256:7d298a1bd60c067ea75d9f684f5f3992c9d6766fadbc0bcedd39750bf344c2f4", 365 | "sha256:8b77946fd508cbf0fccd8e400a7f71d4ac0e1595812e66025bac475a8e811694", 366 | "sha256:8d7e43bd40f65f7d97ad8ef5c9b1778943d02f04febef12def25f7583d19baac", 367 | "sha256:8df133a2ea5e74eef5e8fc6f19b9e085f758768a16e9877a60aec455ed2609b2", 368 | "sha256:8ed74706b26ad100433da4b9d807eae371efaa266ffc3e9191ea436087a9d6a7", 369 | "sha256:92af161ecbdb2883c4593d5ed4815ea71b31fafd7fd05789b23100d081ecac96", 370 | "sha256:97047f0d25cd4bcae81f9ec9dc290ca3e15927c192df17331b53bebe0e3ff96d", 371 | "sha256:9719fe17307a9e814580af1f5c6e05ca593b12fb7e44fe62450a5384dbf61b4b", 372 | "sha256:9767e79108424fb6c3edf8f81e6730666a50feb01a328f4a016464a5893f835a", 373 | "sha256:9a92d3faef50658dd2c5470af249985782bf754c4e18e15afb67d3ab06233f13", 374 | "sha256:9bb6ad405121241e99a86efff22d3ef469024ce22875a7ae045896ad23ba2340", 375 | "sha256:9e28c51fa0ce5674be9f560c6761c1b441631901993f76700b1b30ca6c8378d6", 376 | "sha256:aca086dc5f9ef98c512bac8efea4483eb84abbf926eaeedf7b91479feb092458", 377 | "sha256:ae8b9c6deb1e634ba4f1930eb67ef6e6bf6a44b6eb5ad605642b2d6d5ed9ce3c", 378 | "sha256:b0a545b46b526d418eb91754565ba5b63b1c0b12f9bd2f808c852d9b4b2f9b5c", 379 | "sha256:b4e4bc18382088514ebde9328da057775055940a1f2e18f6ad2d78aa0f3ec5b9", 380 | "sha256:b6420a005548ad52154c8ceab4a1290ff78d757f9e5cbc68f8c77089acd3c432", 381 | "sha256:b86164d2cff4d3aaa1f04a14685cbc072efd0b4f99ca5708b2ad1b9b5988a991", 382 | "sha256:bb3bb49c7a6ad9d981d734ef7c7193bc349ac338776a0360cc671eaee89bcf69", 383 | "sha256:bef4e656f7d98aaa3486d2627e7d2df1157d7e88e7efd43a65aa5dd4714916cf", 384 | "sha256:c0781a98ff5e6586926293e59480b64ddd46282953203c76ae15dbbbf302e8bb", 385 | "sha256:c2006f5c8d28dee289f7020f721354362fa304acbaaf9745751ac4006650254b", 386 | "sha256:c41bfca0bd3532d53d16fd34d20806d5c2b1ace22a2f2e4c0008570bf2c58833", 387 | "sha256:cd47b4a0d41d2afa3e58e5bf1f62069255aa2fd6ff5ee41604418ca925911d76", 388 | "sha256:cdb650fc86227eba20de1a29d4b2c1bfe139dc75a0669270033cb2ea3d391b85", 389 | "sha256:cef2502e7e8a96fe5ad686d60b49e1ab03e438bd9123987994528febd569868e", 390 | "sha256:d27be7405547d1f958b60837dc4c1007da90b8b23f54ba1f8b728c78fdb19d50", 391 | "sha256:d37017287a7adb6ab77e1c5bee9bcf9660f90ff445042b790402a654d2ad81d8", 392 | "sha256:d3ff32724f98fbbbfa9f49d82852b159e9784d6094983d9a8b7f2ddaebb063d4", 393 | "sha256:d73d8ecf8ecf10a3bd007f2192725a34bd62898e8da27eb9d32a58084f93962b", 394 | "sha256:dd708cf4ee4408cf46a48b108fb9427bfa00b9b85812a9262b5c668af2533ea5", 395 | "sha256:e3cd95e10c2610c360154afdc2f1480aea394f4a4f1ea0a5eacce49640c9b190", 396 | "sha256:e4da8ca0c0c0aea88fd46be8e44bd49716772358d648cce45fe387f7b92374a7", 397 | "sha256:eadfbbbfb41b44034a4c757fd5d70baccd43296fb894dba0295606a7cf3124aa", 398 | "sha256:ed667f49b11360951e201453fc3967344d0d0263aa415e1619e85ae7fd17b4e0", 399 | "sha256:f3df3db1d336b9356dd3112eae5f5c2b8b377f3bc826848567f10bfddfee77e9", 400 | "sha256:f6bdac493b949141b733c5345b6ba8f87a226029cbabc7e9e121a413e49441e0", 401 | "sha256:fbf521479bcac1e25a663df882c46a641a9bff6b56dc8b0fafaebd2f66fb231b", 402 | "sha256:fc9b106a1bf918db68619fdcd6d5ad4f972fdd19c01d19bdb6bf63f3589a9ec5", 403 | "sha256:fcdd00edfd0a3001e0181eab3e63bd5c74ad3e67152c84f93f13769a40e073a7", 404 | "sha256:fe4bda6bd4340caa6e5cf95e73f8fea5c4bfc55763dd42f1b50a94c1b4a2fbd4" 405 | ], 406 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 407 | "version": "==4.9.3" 408 | }, 409 | "packaging": { 410 | "hashes": [ 411 | "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", 412 | "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" 413 | ], 414 | "markers": "python_version >= '3.7'", 415 | "version": "==23.2" 416 | }, 417 | "parsel": { 418 | "hashes": [ 419 | "sha256:2708fc74daeeb4ce471e2c2e9089b650ec940c7a218053e57421e69b5b00f82c", 420 | "sha256:aff28e68c9b3f1a901db2a4e3f158d8480a38724d7328ee751c1a4e1c1801e39" 421 | ], 422 | "markers": "python_version >= '3.7'", 423 | "version": "==1.8.1" 424 | }, 425 | "protego": { 426 | "hashes": [ 427 | "sha256:04228bffde4c6bcba31cf6529ba2cfd6e1b70808fdc1d2cb4301be6b28d6c568", 428 | "sha256:db38f6a945839d8162a4034031a21490469566a2726afb51d668497c457fb0aa" 429 | ], 430 | "markers": "python_version >= '3.7'", 431 | "version": "==0.3.0" 432 | }, 433 | "pyasn1": { 434 | "hashes": [ 435 | "sha256:87a2121042a1ac9358cabcaf1d07680ff97ee6404333bacca15f76aa8ad01a57", 436 | "sha256:97b7290ca68e62a832558ec3976f15cbf911bf5d7c7039d8b861c2a0ece69fde" 437 | ], 438 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", 439 | "version": "==0.5.0" 440 | }, 441 | "pyasn1-modules": { 442 | "hashes": [ 443 | "sha256:5bd01446b736eb9d31512a30d46c1ac3395d676c6f3cafa4c03eb54b9925631c", 444 | "sha256:d3ccd6ed470d9ffbc716be08bd90efbd44d0734bc9303818f7336070984a162d" 445 | ], 446 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", 447 | "version": "==0.3.0" 448 | }, 449 | "pycparser": { 450 | "hashes": [ 451 | "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", 452 | "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206" 453 | ], 454 | "version": "==2.21" 455 | }, 456 | "pydispatcher": { 457 | "hashes": [ 458 | "sha256:96543bea04115ffde08f851e1d45cacbfd1ee866ac42127d9b476dc5aefa7de0", 459 | "sha256:b777c6ad080dc1bad74a4c29d6a46914fa6701ac70f94b0d66fbcfde62f5be31" 460 | ], 461 | "markers": "platform_python_implementation == 'CPython'", 462 | "version": "==2.0.7" 463 | }, 464 | "pymongo": { 465 | "hashes": [ 466 | "sha256:076afa0a4a96ca9f77fec0e4a0d241200b3b3a1766f8d7be9a905ecf59a7416b", 467 | "sha256:08819da7864f9b8d4a95729b2bea5fffed08b63d3b9c15b4fea47de655766cf5", 468 | "sha256:0a1f26bc1f5ce774d99725773901820dfdfd24e875028da4a0252a5b48dcab5c", 469 | "sha256:0f4b125b46fe377984fbaecf2af40ed48b05a4b7676a2ff98999f2016d66b3ec", 470 | "sha256:1240edc1a448d4ada4bf1a0e55550b6292420915292408e59159fd8bbdaf8f63", 471 | "sha256:152259f0f1a60f560323aacf463a3642a65a25557683f49cfa08c8f1ecb2395a", 472 | "sha256:168172ef7856e20ec024fe2a746bfa895c88b32720138e6438fd765ebd2b62dd", 473 | "sha256:1b1d7d9aabd8629a31d63cd106d56cca0e6420f38e50563278b520f385c0d86e", 474 | "sha256:1d40ad09d9f5e719bc6f729cc6b17f31c0b055029719406bd31dde2f72fca7e7", 475 | "sha256:21b953da14549ff62ea4ae20889c71564328958cbdf880c64a92a48dda4c9c53", 476 | "sha256:23cc6d7eb009c688d70da186b8f362d61d5dd1a2c14a45b890bd1e91e9c451f2", 477 | "sha256:2988ef5e6b360b3ff1c6d55c53515499de5f48df31afd9f785d788cdacfbe2d3", 478 | "sha256:2a0aade2b11dc0c326ccd429ee4134d2d47459ff68d449c6d7e01e74651bd255", 479 | "sha256:2b0176f9233a5927084c79ff80b51bd70bfd57e4f3d564f50f80238e797f0c8a", 480 | "sha256:2d4fa1b01fa7e5b7bb8d312e3542e211b320eb7a4e3d8dc884327039d93cb9e0", 481 | "sha256:3236cf89d69679eaeb9119c840f5c7eb388a2110b57af6bb6baf01a1da387c18", 482 | "sha256:33faa786cc907de63f745f587e9879429b46033d7d97a7b84b37f4f8f47b9b32", 483 | "sha256:37df8f6006286a5896d1cbc3efb8471ced42e3568d38e6cb00857277047b0d63", 484 | "sha256:3a7166d57dc74d679caa7743b8ecf7dc3a1235a9fd178654dddb2b2a627ae229", 485 | "sha256:3d79ae3bb1ff041c0db56f138c88ce1dfb0209f3546d8d6e7c3f74944ecd2439", 486 | "sha256:3e33064f1984db412b34d51496f4ea785a9cff621c67de58e09fb28da6468a52", 487 | "sha256:3fa3648e4f1e63ddfe53563ee111079ea3ab35c3b09cd25bc22dadc8269a495f", 488 | "sha256:40d5f6e853ece9bfc01e9129b228df446f49316a4252bb1fbfae5c3c9dedebad", 489 | "sha256:41771b22dd2822540f79a877c391283d4e6368125999a5ec8beee1ce566f3f82", 490 | "sha256:435228d3c16a375274ac8ab9c4f9aef40c5e57ddb8296e20ecec9e2461da1017", 491 | "sha256:44ee985194c426ddf781fa784f31ffa29cb59657b2dba09250a4245431847d73", 492 | "sha256:465fd5b040206f8bce7016b01d7e7f79d2fcd7c2b8e41791be9632a9df1b4999", 493 | "sha256:496c9cbcb4951183d4503a9d7d2c1e3694aab1304262f831d5e1917e60386036", 494 | "sha256:49dce6957598975d8b8d506329d2a3a6c4aee911fa4bbcf5e52ffc6897122950", 495 | "sha256:4c42748ccc451dfcd9cef6c5447a7ab727351fd9747ad431db5ebb18a9b78a4d", 496 | "sha256:505f8519c4c782a61d94a17b0da50be639ec462128fbd10ab0a34889218fdee3", 497 | "sha256:53f2dda54d76a98b43a410498bd12f6034b2a14b6844ca08513733b2b20b7ad8", 498 | "sha256:56320c401f544d762fc35766936178fbceb1d9261cd7b24fbfbc8fb6f67aa8a5", 499 | "sha256:58a63a26a1e3dc481dd3a18d6d9f8bd1d576cd1ffe0d479ba7dd38b0aeb20066", 500 | "sha256:5caee7bd08c3d36ec54617832b44985bd70c4cbd77c5b313de6f7fce0bb34f93", 501 | "sha256:631492573a1bef2f74f9ac0f9d84e0ce422c251644cd81207530af4aa2ee1980", 502 | "sha256:63d8019eee119df308a075b8a7bdb06d4720bf791e2b73d5ab0e7473c115d79c", 503 | "sha256:6422b6763b016f2ef2beedded0e546d6aa6ba87910f9244d86e0ac7690f75c96", 504 | "sha256:681f252e43b3ef054ca9161635f81b730f4d8cadd28b3f2b2004f5a72f853982", 505 | "sha256:6d64878d1659d2a5bdfd0f0a4d79bafe68653c573681495e424ab40d7b6d6d41", 506 | "sha256:74c0da07c04d0781490b2915e7514b1adb265ef22af039a947988c331ee7455b", 507 | "sha256:7591a3beea6a9a4fa3080d27d193b41f631130e3ffa76b88c9ccea123f26dc59", 508 | "sha256:76a262c41c1a7cbb84a3b11976578a7eb8e788c4b7bfbd15c005fb6ca88e6e50", 509 | "sha256:77cfff95c1fafd09e940b3fdcb7b65f11442662fad611d0e69b4dd5d17a81c60", 510 | "sha256:8027c9063579083746147cf401a7072a9fb6829678076cd3deff28bb0e0f50c8", 511 | "sha256:80a167081c75cf66b32f30e2f1eaee9365af935a86dbd76788169911bed9b5d5", 512 | "sha256:840eaf30ccac122df260b6005f9dfae4ac287c498ee91e3e90c56781614ca238", 513 | "sha256:8543253adfaa0b802bfa88386db1009c6ebb7d5684d093ee4edc725007553d21", 514 | "sha256:89b3f2da57a27913d15d2a07d58482f33d0a5b28abd20b8e643ab4d625e36257", 515 | "sha256:8e559116e4128630ad3b7e788e2e5da81cbc2344dee246af44471fa650486a70", 516 | "sha256:9aff6279e405dc953eeb540ab061e72c03cf38119613fce183a8e94f31be608f", 517 | "sha256:9c04b9560872fa9a91251030c488e0a73bce9321a70f991f830c72b3f8115d0d", 518 | "sha256:9d2346b00af524757576cc2406414562cced1d4349c92166a0ee377a2a483a80", 519 | "sha256:a253b765b7cbc4209f1d8ee16c7287c4268d3243070bf72d7eec5aa9dfe2a2c2", 520 | "sha256:a8127437ebc196a6f5e8fddd746bd0903a400dc6b5ae35df672dd1ccc7170a2a", 521 | "sha256:b25f7bea162b3dbec6d33c522097ef81df7c19a9300722fa6853f5b495aecb77", 522 | "sha256:b33c17d9e694b66d7e96977e9e56df19d662031483efe121a24772a44ccbbc7e", 523 | "sha256:b4fe46b58010115514b842c669a0ed9b6a342017b15905653a5b1724ab80917f", 524 | "sha256:b520aafc6cb148bac09ccf532f52cbd31d83acf4d3e5070d84efe3c019a1adbf", 525 | "sha256:b5bbb87fa0511bd313d9a2c90294c88db837667c2bda2ea3fa7a35b59fd93b1f", 526 | "sha256:b6d2a56fc2354bb6378f3634402eec788a8f3facf0b3e7d468db5f2b5a78d763", 527 | "sha256:bbd705d5f3c3d1ff2d169e418bb789ff07ab3c70d567cc6ba6b72b04b9143481", 528 | "sha256:bc5d8c3647b8ae28e4312f1492b8f29deebd31479cd3abaa989090fb1d66db83", 529 | "sha256:c3c3525ea8658ee1192cdddf5faf99b07ebe1eeaa61bf32821126df6d1b8072b", 530 | "sha256:c9a9a39b7cac81dca79fca8c2a6479ef4c7b1aab95fad7544cc0e8fd943595a2", 531 | "sha256:cd4c8d6aa91d3e35016847cbe8d73106e3d1c9a4e6578d38e2c346bfe8edb3ca", 532 | "sha256:cf62da7a4cdec9a4b2981fcbd5e08053edffccf20e845c0b6ec1e77eb7fab61d", 533 | "sha256:d67225f05f6ea27c8dc57f3fa6397c96d09c42af69d46629f71e82e66d33fa4f", 534 | "sha256:dfcd2b9f510411de615ccedd47462dae80e82fdc09fe9ab0f0f32f11cf57eeb5", 535 | "sha256:e1f61355c821e870fb4c17cdb318669cfbcf245a291ce5053b41140870c3e5cc", 536 | "sha256:e249190b018d63c901678053b4a43e797ca78b93fb6d17633e3567d4b3ec6107", 537 | "sha256:e2654d1278384cff75952682d17c718ecc1ad1d6227bb0068fd826ba47d426a5", 538 | "sha256:e57d859b972c75ee44ea2ef4758f12821243e99de814030f69a3decb2aa86807", 539 | "sha256:e5a27f348909235a106a3903fc8e70f573d89b41d723a500869c6569a391cff7", 540 | "sha256:ead4f19d0257a756b21ac2e0e85a37a7245ddec36d3b6008d5bfe416525967dc", 541 | "sha256:f076b779aa3dc179aa3ed861be063a313ed4e48ae9f6a8370a9b1295d4502111", 542 | "sha256:f1bb3a62395ffe835dbef3a1cbff48fbcce709c78bd1f52e896aee990928432b", 543 | "sha256:f2227a08b091bd41df5aadee0a5037673f691e2aa000e1968b1ea2342afc6880", 544 | "sha256:f3754acbd7efc7f1b529039fcffc092a15e1cf045e31f22f6c9c5950c613ec4d", 545 | "sha256:fe48f50fb6348511a3268a893bfd4ab5f263f5ac220782449d03cd05964d1ae7", 546 | "sha256:fff7d17d30b2cd45afd654b3fc117755c5d84506ed25fda386494e4e0a3416e1" 547 | ], 548 | "index": "pypi", 549 | "markers": "python_version >= '3.7'", 550 | "version": "==4.5.0" 551 | }, 552 | "pyopenssl": { 553 | "hashes": [ 554 | "sha256:24f0dc5227396b3e831f4c7f602b950a5e9833d292c8e4a2e06b709292806ae2", 555 | "sha256:276f931f55a452e7dea69c7173e984eb2a4407ce413c918aa34b55f82f9b8bac" 556 | ], 557 | "markers": "python_version >= '3.6'", 558 | "version": "==23.2.0" 559 | }, 560 | "python-slugify": { 561 | "hashes": [ 562 | "sha256:70ca6ea68fe63ecc8fa4fcf00ae651fc8a5d02d93dcd12ae6d4fc7ca46c4d395", 563 | "sha256:ce0d46ddb668b3be82f4ed5e503dbc33dd815d83e2eb6824211310d3fb172a27" 564 | ], 565 | "index": "pypi", 566 | "markers": "python_version >= '3.7'", 567 | "version": "==8.0.1" 568 | }, 569 | "pyyaml": { 570 | "hashes": [ 571 | "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5", 572 | "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc", 573 | "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df", 574 | "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741", 575 | "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206", 576 | "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27", 577 | "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595", 578 | "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62", 579 | "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98", 580 | "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696", 581 | "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290", 582 | "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9", 583 | "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d", 584 | "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6", 585 | "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867", 586 | "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47", 587 | "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486", 588 | "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6", 589 | "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3", 590 | "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007", 591 | "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938", 592 | "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0", 593 | "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c", 594 | "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735", 595 | "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d", 596 | "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28", 597 | "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4", 598 | "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba", 599 | "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8", 600 | "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5", 601 | "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd", 602 | "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3", 603 | "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0", 604 | "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515", 605 | "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c", 606 | "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c", 607 | "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924", 608 | "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34", 609 | "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43", 610 | "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859", 611 | "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673", 612 | "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54", 613 | "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a", 614 | "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b", 615 | "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab", 616 | "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa", 617 | "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c", 618 | "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585", 619 | "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d", 620 | "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f" 621 | ], 622 | "index": "pypi", 623 | "markers": "python_version >= '3.6'", 624 | "version": "==6.0.1" 625 | }, 626 | "queuelib": { 627 | "hashes": [ 628 | "sha256:4b207267f2642a8699a1f806045c56eb7ad1a85a10c0e249884580d139c2fcd2", 629 | "sha256:4b96d48f650a814c6fb2fd11b968f9c46178b683aad96d68f930fe13a8574d19" 630 | ], 631 | "markers": "python_version >= '3.5'", 632 | "version": "==1.6.2" 633 | }, 634 | "requests": { 635 | "hashes": [ 636 | "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", 637 | "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" 638 | ], 639 | "markers": "python_version >= '3.7'", 640 | "version": "==2.31.0" 641 | }, 642 | "requests-file": { 643 | "hashes": [ 644 | "sha256:07d74208d3389d01c38ab89ef403af0cfec63957d53a0081d8eca738d0247d8e", 645 | "sha256:dfe5dae75c12481f68ba353183c53a65e6044c923e64c24b2209f6c7570ca953" 646 | ], 647 | "version": "==1.5.1" 648 | }, 649 | "scrapy": { 650 | "hashes": [ 651 | "sha256:3cbdedce0c3f0e0482d61be2d7458683be7cd7cf14b0ee6adfbaddb80f5b36a5", 652 | "sha256:a7f36544d1f5ceb13cff9b7bc904bd7c0fc43a3af0fbe5aa2034fd937cf092d1" 653 | ], 654 | "index": "pypi", 655 | "markers": "python_version >= '3.8'", 656 | "version": "==2.11.0" 657 | }, 658 | "service-identity": { 659 | "hashes": [ 660 | "sha256:87415a691d52fcad954a500cb81f424d0273f8e7e3ee7d766128f4575080f383", 661 | "sha256:ecb33cd96307755041e978ab14f8b14e13b40f1fbd525a4dc78f46d2b986431d" 662 | ], 663 | "markers": "python_version >= '3.8'", 664 | "version": "==23.1.0" 665 | }, 666 | "setuptools": { 667 | "hashes": [ 668 | "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87", 669 | "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a" 670 | ], 671 | "markers": "python_version >= '3.8'", 672 | "version": "==68.2.2" 673 | }, 674 | "sgmllib3k": { 675 | "hashes": [ 676 | "sha256:7868fb1c8bfa764c1ac563d3cf369c381d1325d36124933a726f29fcdaa812e9" 677 | ], 678 | "version": "==1.0.0" 679 | }, 680 | "six": { 681 | "hashes": [ 682 | "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", 683 | "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" 684 | ], 685 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 686 | "version": "==1.16.0" 687 | }, 688 | "text-unidecode": { 689 | "hashes": [ 690 | "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", 691 | "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93" 692 | ], 693 | "version": "==1.3" 694 | }, 695 | "tldextract": { 696 | "hashes": [ 697 | "sha256:1f4938a58ea6aa3fc5b374e22c9477ed43159a26c61a5c51a0bf0fdd694fd5e1", 698 | "sha256:959965f3a4715105c598ef44ef624db9c9f85ee201cbfc2e063a51f8f19b1a5b" 699 | ], 700 | "markers": "python_version >= '3.8'", 701 | "version": "==5.0.0" 702 | }, 703 | "twisted": { 704 | "hashes": [ 705 | "sha256:32acbd40a94f5f46e7b42c109bfae2b302250945561783a8b7a059048f2d4d31", 706 | "sha256:86c55f712cc5ab6f6d64e02503352464f0400f66d4f079096d744080afcccbd0" 707 | ], 708 | "markers": "python_full_version >= '3.7.1'", 709 | "version": "==22.10.0" 710 | }, 711 | "typing-extensions": { 712 | "hashes": [ 713 | "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0", 714 | "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef" 715 | ], 716 | "markers": "python_version >= '3.8'", 717 | "version": "==4.8.0" 718 | }, 719 | "urllib3": { 720 | "hashes": [ 721 | "sha256:7a7c7003b000adf9e7ca2a377c9688bbc54ed41b985789ed576570342a375cd2", 722 | "sha256:b19e1a85d206b56d7df1d5e683df4a7725252a964e3993648dd0fb5a1c157564" 723 | ], 724 | "markers": "python_version >= '3.7'", 725 | "version": "==2.0.6" 726 | }, 727 | "w3lib": { 728 | "hashes": [ 729 | "sha256:c4432926e739caa8e3f49f5de783f336df563d9490416aebd5d39fb896d264e7", 730 | "sha256:ed5b74e997eea2abe3c1321f916e344144ee8e9072a6f33463ee8e57f858a4b1" 731 | ], 732 | "markers": "python_version >= '3.7'", 733 | "version": "==2.1.2" 734 | }, 735 | "zope.interface": { 736 | "hashes": [ 737 | "sha256:0c8cf55261e15590065039696607f6c9c1aeda700ceee40c70478552d323b3ff", 738 | "sha256:13b7d0f2a67eb83c385880489dbb80145e9d344427b4262c49fbf2581677c11c", 739 | "sha256:1f294a15f7723fc0d3b40701ca9b446133ec713eafc1cc6afa7b3d98666ee1ac", 740 | "sha256:239a4a08525c080ff833560171d23b249f7f4d17fcbf9316ef4159f44997616f", 741 | "sha256:2f8d89721834524a813f37fa174bac074ec3d179858e4ad1b7efd4401f8ac45d", 742 | "sha256:2fdc7ccbd6eb6b7df5353012fbed6c3c5d04ceaca0038f75e601060e95345309", 743 | "sha256:34c15ca9248f2e095ef2e93af2d633358c5f048c49fbfddf5fdfc47d5e263736", 744 | "sha256:387545206c56b0315fbadb0431d5129c797f92dc59e276b3ce82db07ac1c6179", 745 | "sha256:43b576c34ef0c1f5a4981163b551a8781896f2a37f71b8655fd20b5af0386abb", 746 | "sha256:57d0a8ce40ce440f96a2c77824ee94bf0d0925e6089df7366c2272ccefcb7941", 747 | "sha256:5a804abc126b33824a44a7aa94f06cd211a18bbf31898ba04bd0924fbe9d282d", 748 | "sha256:67be3ca75012c6e9b109860820a8b6c9a84bfb036fbd1076246b98e56951ca92", 749 | "sha256:6af47f10cfc54c2ba2d825220f180cc1e2d4914d783d6fc0cd93d43d7bc1c78b", 750 | "sha256:6dc998f6de015723196a904045e5a2217f3590b62ea31990672e31fbc5370b41", 751 | "sha256:70d2cef1bf529bff41559be2de9d44d47b002f65e17f43c73ddefc92f32bf00f", 752 | "sha256:7ebc4d34e7620c4f0da7bf162c81978fce0ea820e4fa1e8fc40ee763839805f3", 753 | "sha256:964a7af27379ff4357dad1256d9f215047e70e93009e532d36dcb8909036033d", 754 | "sha256:97806e9ca3651588c1baaebb8d0c5ee3db95430b612db354c199b57378312ee8", 755 | "sha256:9b9bc671626281f6045ad61d93a60f52fd5e8209b1610972cf0ef1bbe6d808e3", 756 | "sha256:9ffdaa5290422ac0f1688cb8adb1b94ca56cee3ad11f29f2ae301df8aecba7d1", 757 | "sha256:a0da79117952a9a41253696ed3e8b560a425197d4e41634a23b1507efe3273f1", 758 | "sha256:a41f87bb93b8048fe866fa9e3d0c51e27fe55149035dcf5f43da4b56732c0a40", 759 | "sha256:aa6fd016e9644406d0a61313e50348c706e911dca29736a3266fc9e28ec4ca6d", 760 | "sha256:ad54ed57bdfa3254d23ae04a4b1ce405954969c1b0550cc2d1d2990e8b439de1", 761 | "sha256:b012d023b4fb59183909b45d7f97fb493ef7a46d2838a5e716e3155081894605", 762 | "sha256:b51b64432eed4c0744241e9ce5c70dcfecac866dff720e746d0a9c82f371dfa7", 763 | "sha256:bbe81def9cf3e46f16ce01d9bfd8bea595e06505e51b7baf45115c77352675fd", 764 | "sha256:c9559138690e1bd4ea6cd0954d22d1e9251e8025ce9ede5d0af0ceae4a401e43", 765 | "sha256:e30506bcb03de8983f78884807e4fd95d8db6e65b69257eea05d13d519b83ac0", 766 | "sha256:e33e86fd65f369f10608b08729c8f1c92ec7e0e485964670b4d2633a4812d36b", 767 | "sha256:e441e8b7d587af0414d25e8d05e27040d78581388eed4c54c30c0c91aad3a379", 768 | "sha256:e8bb9c990ca9027b4214fa543fd4025818dc95f8b7abce79d61dc8a2112b561a", 769 | "sha256:ef43ee91c193f827e49599e824385ec7c7f3cd152d74cb1dfe02cb135f264d83", 770 | "sha256:ef467d86d3cfde8b39ea1b35090208b0447caaabd38405420830f7fd85fbdd56", 771 | "sha256:f89b28772fc2562ed9ad871c865f5320ef761a7fcc188a935e21fe8b31a38ca9", 772 | "sha256:fddbab55a2473f1d3b8833ec6b7ac31e8211b0aa608df5ab09ce07f3727326de" 773 | ], 774 | "markers": "python_version >= '3.7'", 775 | "version": "==6.1" 776 | } 777 | }, 778 | "develop": { 779 | "appnope": { 780 | "hashes": [ 781 | "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24", 782 | "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e" 783 | ], 784 | "markers": "platform_system == 'Darwin'", 785 | "version": "==0.1.3" 786 | }, 787 | "asttokens": { 788 | "hashes": [ 789 | "sha256:2e0171b991b2c959acc6c49318049236844a5da1d65ba2672c4880c1c894834e", 790 | "sha256:cf8fc9e61a86461aa9fb161a14a0841a03c405fa829ac6b202670b3495d2ce69" 791 | ], 792 | "version": "==2.4.0" 793 | }, 794 | "backcall": { 795 | "hashes": [ 796 | "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e", 797 | "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255" 798 | ], 799 | "version": "==0.2.0" 800 | }, 801 | "comm": { 802 | "hashes": [ 803 | "sha256:354e40a59c9dd6db50c5cc6b4acc887d82e9603787f83b68c01a80a923984d15", 804 | "sha256:6d52794cba11b36ed9860999cd10fd02d6b2eac177068fdd585e1e2f8a96e67a" 805 | ], 806 | "markers": "python_version >= '3.6'", 807 | "version": "==0.1.4" 808 | }, 809 | "debugpy": { 810 | "hashes": [ 811 | "sha256:125b9a637e013f9faac0a3d6a82bd17c8b5d2c875fb6b7e2772c5aba6d082332", 812 | "sha256:12af2c55b419521e33d5fb21bd022df0b5eb267c3e178f1d374a63a2a6bdccd0", 813 | "sha256:3c6fb41c98ec51dd010d7ed650accfd07a87fe5e93eca9d5f584d0578f28f35f", 814 | "sha256:46ab6780159eeabb43c1495d9c84cf85d62975e48b6ec21ee10c95767c0590aa", 815 | "sha256:57161629133113c97b387382045649a2b985a348f0c9366e22217c87b68b73c6", 816 | "sha256:5d9de202f5d42e62f932507ee8b21e30d49aae7e46d5b1dd5c908db1d7068637", 817 | "sha256:60009b132c91951354f54363f8ebdf7457aeb150e84abba5ae251b8e9f29a8a6", 818 | "sha256:61eab4a4c8b6125d41a34bad4e5fe3d2cc145caecd63c3fe953be4cc53e65bf8", 819 | "sha256:7fb95ca78f7ac43393cd0e0f2b6deda438ec7c5e47fa5d38553340897d2fbdfb", 820 | "sha256:8cd0197141eb9e8a4566794550cfdcdb8b3db0818bdf8c49a8e8f8053e56e38b", 821 | "sha256:9c9b0ac1ce2a42888199df1a1906e45e6f3c9555497643a85e0bf2406e3ffbc4", 822 | "sha256:a64093656c4c64dc6a438e11d59369875d200bd5abb8f9b26c1f5f723622e153", 823 | "sha256:a8b7a2fd27cd9f3553ac112f356ad4ca93338feadd8910277aff71ab24d8775f", 824 | "sha256:b05a6b503ed520ad58c8dc682749113d2fd9f41ffd45daec16e558ca884008cd", 825 | "sha256:bdc5ef99d14b9c0fcb35351b4fbfc06ac0ee576aeab6b2511702e5a648a2e595", 826 | "sha256:e3412f9faa9ade82aa64a50b602544efcba848c91384e9f93497a458767e6926", 827 | "sha256:ef54404365fae8d45cf450d0544ee40cefbcb9cb85ea7afe89a963c27028261e", 828 | "sha256:ef9ab7df0b9a42ed9c878afd3eaaff471fce3fa73df96022e1f5c9f8f8c87ada" 829 | ], 830 | "markers": "python_version >= '3.8'", 831 | "version": "==1.8.0" 832 | }, 833 | "decorator": { 834 | "hashes": [ 835 | "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330", 836 | "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186" 837 | ], 838 | "markers": "python_version >= '3.5'", 839 | "version": "==5.1.1" 840 | }, 841 | "executing": { 842 | "hashes": [ 843 | "sha256:06df6183df67389625f4e763921c6cf978944721abf3e714000200aab95b0657", 844 | "sha256:0ff053696fdeef426cda5bd18eacd94f82c91f49823a2e9090124212ceea9b08" 845 | ], 846 | "version": "==2.0.0" 847 | }, 848 | "iniconfig": { 849 | "hashes": [ 850 | "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", 851 | "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" 852 | ], 853 | "markers": "python_version >= '3.7'", 854 | "version": "==2.0.0" 855 | }, 856 | "ipykernel": { 857 | "hashes": [ 858 | "sha256:2e2ee359baba19f10251b99415bb39de1e97d04e1fab385646f24f0596510b77", 859 | "sha256:f468ddd1f17acb48c8ce67fcfa49ba6d46d4f9ac0438c1f441be7c3d1372230b" 860 | ], 861 | "index": "pypi", 862 | "markers": "python_version >= '3.8'", 863 | "version": "==6.25.2" 864 | }, 865 | "ipython": { 866 | "hashes": [ 867 | "sha256:0852469d4d579d9cd613c220af7bf0c9cc251813e12be647cb9d463939db9b1e", 868 | "sha256:ad52f58fca8f9f848e256c629eff888efc0528c12fe0f8ec14f33205f23ef938" 869 | ], 870 | "markers": "python_version >= '3.9'", 871 | "version": "==8.16.1" 872 | }, 873 | "jedi": { 874 | "hashes": [ 875 | "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd", 876 | "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0" 877 | ], 878 | "markers": "python_version >= '3.6'", 879 | "version": "==0.19.1" 880 | }, 881 | "jupyter-client": { 882 | "hashes": [ 883 | "sha256:6a2a950ec23a8f62f9e4c66acec7f0ea6c7d1f80ba0992e747b10c56ce2e6dbe", 884 | "sha256:dc1b857d5d7d76ac101766c6e9b646bf18742721126e72e5d484c75a993cada2" 885 | ], 886 | "markers": "python_version >= '3.8'", 887 | "version": "==8.4.0" 888 | }, 889 | "jupyter-core": { 890 | "hashes": [ 891 | "sha256:66e252f675ac04dcf2feb6ed4afb3cd7f68cf92f483607522dc251f32d471571", 892 | "sha256:e4b98344bb94ee2e3e6c4519a97d001656009f9cb2b7f2baf15b3c205770011d" 893 | ], 894 | "markers": "python_version >= '3.8'", 895 | "version": "==5.4.0" 896 | }, 897 | "matplotlib-inline": { 898 | "hashes": [ 899 | "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311", 900 | "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304" 901 | ], 902 | "markers": "python_version >= '3.5'", 903 | "version": "==0.1.6" 904 | }, 905 | "nest-asyncio": { 906 | "hashes": [ 907 | "sha256:25aa2ca0d2a5b5531956b9e273b45cf664cae2b145101d73b86b199978d48fdb", 908 | "sha256:accda7a339a70599cb08f9dd09a67e0c2ef8d8d6f4c07f96ab203f2ae254e48d" 909 | ], 910 | "markers": "python_version >= '3.5'", 911 | "version": "==1.5.8" 912 | }, 913 | "packaging": { 914 | "hashes": [ 915 | "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", 916 | "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" 917 | ], 918 | "markers": "python_version >= '3.7'", 919 | "version": "==23.2" 920 | }, 921 | "parso": { 922 | "hashes": [ 923 | "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0", 924 | "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75" 925 | ], 926 | "markers": "python_version >= '3.6'", 927 | "version": "==0.8.3" 928 | }, 929 | "pexpect": { 930 | "hashes": [ 931 | "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937", 932 | "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c" 933 | ], 934 | "markers": "sys_platform != 'win32'", 935 | "version": "==4.8.0" 936 | }, 937 | "pickleshare": { 938 | "hashes": [ 939 | "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca", 940 | "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56" 941 | ], 942 | "version": "==0.7.5" 943 | }, 944 | "platformdirs": { 945 | "hashes": [ 946 | "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3", 947 | "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e" 948 | ], 949 | "markers": "python_version >= '3.7'", 950 | "version": "==3.11.0" 951 | }, 952 | "pluggy": { 953 | "hashes": [ 954 | "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12", 955 | "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7" 956 | ], 957 | "markers": "python_version >= '3.8'", 958 | "version": "==1.3.0" 959 | }, 960 | "prompt-toolkit": { 961 | "hashes": [ 962 | "sha256:04505ade687dc26dc4284b1ad19a83be2f2afe83e7a828ace0c72f3a1df72aac", 963 | "sha256:9dffbe1d8acf91e3de75f3b544e4842382fc06c6babe903ac9acb74dc6e08d88" 964 | ], 965 | "markers": "python_full_version >= '3.7.0'", 966 | "version": "==3.0.39" 967 | }, 968 | "psutil": { 969 | "hashes": [ 970 | "sha256:104a5cc0e31baa2bcf67900be36acde157756b9c44017b86b2c049f11957887d", 971 | "sha256:3c6f686f4225553615612f6d9bc21f1c0e305f75d7d8454f9b46e901778e7217", 972 | "sha256:4aef137f3345082a3d3232187aeb4ac4ef959ba3d7c10c33dd73763fbc063da4", 973 | "sha256:5410638e4df39c54d957fc51ce03048acd8e6d60abc0f5107af51e5fb566eb3c", 974 | "sha256:5b9b8cb93f507e8dbaf22af6a2fd0ccbe8244bf30b1baad6b3954e935157ae3f", 975 | "sha256:7a7dd9997128a0d928ed4fb2c2d57e5102bb6089027939f3b722f3a210f9a8da", 976 | "sha256:89518112647f1276b03ca97b65cc7f64ca587b1eb0278383017c2a0dcc26cbe4", 977 | "sha256:8c5f7c5a052d1d567db4ddd231a9d27a74e8e4a9c3f44b1032762bd7b9fdcd42", 978 | "sha256:ab8ed1a1d77c95453db1ae00a3f9c50227ebd955437bcf2a574ba8adbf6a74d5", 979 | "sha256:acf2aef9391710afded549ff602b5887d7a2349831ae4c26be7c807c0a39fac4", 980 | "sha256:b258c0c1c9d145a1d5ceffab1134441c4c5113b2417fafff7315a917a026c3c9", 981 | "sha256:be8929ce4313f9f8146caad4272f6abb8bf99fc6cf59344a3167ecd74f4f203f", 982 | "sha256:c607bb3b57dc779d55e1554846352b4e358c10fff3abf3514a7a6601beebdb30", 983 | "sha256:ea8518d152174e1249c4f2a1c89e3e6065941df2fa13a1ab45327716a23c2b48" 984 | ], 985 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 986 | "version": "==5.9.5" 987 | }, 988 | "ptyprocess": { 989 | "hashes": [ 990 | "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", 991 | "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220" 992 | ], 993 | "version": "==0.7.0" 994 | }, 995 | "pure-eval": { 996 | "hashes": [ 997 | "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350", 998 | "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3" 999 | ], 1000 | "version": "==0.2.2" 1001 | }, 1002 | "pygments": { 1003 | "hashes": [ 1004 | "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692", 1005 | "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29" 1006 | ], 1007 | "markers": "python_version >= '3.7'", 1008 | "version": "==2.16.1" 1009 | }, 1010 | "pytest": { 1011 | "hashes": [ 1012 | "sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002", 1013 | "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069" 1014 | ], 1015 | "index": "pypi", 1016 | "markers": "python_version >= '3.7'", 1017 | "version": "==7.4.2" 1018 | }, 1019 | "python-dateutil": { 1020 | "hashes": [ 1021 | "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", 1022 | "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" 1023 | ], 1024 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 1025 | "version": "==2.8.2" 1026 | }, 1027 | "pyzmq": { 1028 | "hashes": [ 1029 | "sha256:019e59ef5c5256a2c7378f2fb8560fc2a9ff1d315755204295b2eab96b254d0a", 1030 | "sha256:034239843541ef7a1aee0c7b2cb7f6aafffb005ede965ae9cbd49d5ff4ff73cf", 1031 | "sha256:03b3f49b57264909aacd0741892f2aecf2f51fb053e7d8ac6767f6c700832f45", 1032 | "sha256:047a640f5c9c6ade7b1cc6680a0e28c9dd5a0825135acbd3569cc96ea00b2505", 1033 | "sha256:04ccbed567171579ec2cebb9c8a3e30801723c575601f9a990ab25bcac6b51e2", 1034 | "sha256:057e824b2aae50accc0f9a0570998adc021b372478a921506fddd6c02e60308e", 1035 | "sha256:11baebdd5fc5b475d484195e49bae2dc64b94a5208f7c89954e9e354fc609d8f", 1036 | "sha256:11c1d2aed9079c6b0c9550a7257a836b4a637feb334904610f06d70eb44c56d2", 1037 | "sha256:11d58723d44d6ed4dd677c5615b2ffb19d5c426636345567d6af82be4dff8a55", 1038 | "sha256:12720a53e61c3b99d87262294e2b375c915fea93c31fc2336898c26d7aed34cd", 1039 | "sha256:17ef5f01d25b67ca8f98120d5fa1d21efe9611604e8eb03a5147360f517dd1e2", 1040 | "sha256:18d43df3f2302d836f2a56f17e5663e398416e9dd74b205b179065e61f1a6edf", 1041 | "sha256:1a5d26fe8f32f137e784f768143728438877d69a586ddeaad898558dc971a5ae", 1042 | "sha256:1af379b33ef33757224da93e9da62e6471cf4a66d10078cf32bae8127d3d0d4a", 1043 | "sha256:1ccf825981640b8c34ae54231b7ed00271822ea1c6d8ba1090ebd4943759abf5", 1044 | "sha256:21eb4e609a154a57c520e3d5bfa0d97e49b6872ea057b7c85257b11e78068222", 1045 | "sha256:2243700cc5548cff20963f0ca92d3e5e436394375ab8a354bbea2b12911b20b0", 1046 | "sha256:255ca2b219f9e5a3a9ef3081512e1358bd4760ce77828e1028b818ff5610b87b", 1047 | "sha256:259c22485b71abacdfa8bf79720cd7bcf4b9d128b30ea554f01ae71fdbfdaa23", 1048 | "sha256:25f0e6b78220aba09815cd1f3a32b9c7cb3e02cb846d1cfc526b6595f6046618", 1049 | "sha256:273bc3959bcbff3f48606b28229b4721716598d76b5aaea2b4a9d0ab454ec062", 1050 | "sha256:292fe3fc5ad4a75bc8df0dfaee7d0babe8b1f4ceb596437213821f761b4589f9", 1051 | "sha256:2ca57a5be0389f2a65e6d3bb2962a971688cbdd30b4c0bd188c99e39c234f414", 1052 | "sha256:2d163a18819277e49911f7461567bda923461c50b19d169a062536fffe7cd9d2", 1053 | "sha256:2d81f1ddae3858b8299d1da72dd7d19dd36aab654c19671aa8a7e7fb02f6638a", 1054 | "sha256:2f957ce63d13c28730f7fd6b72333814221c84ca2421298f66e5143f81c9f91f", 1055 | "sha256:330f9e188d0d89080cde66dc7470f57d1926ff2fb5576227f14d5be7ab30b9fa", 1056 | "sha256:34c850ce7976d19ebe7b9d4b9bb8c9dfc7aac336c0958e2651b88cbd46682123", 1057 | "sha256:35b5ab8c28978fbbb86ea54958cd89f5176ce747c1fb3d87356cf698048a7790", 1058 | "sha256:3669cf8ee3520c2f13b2e0351c41fea919852b220988d2049249db10046a7afb", 1059 | "sha256:381469297409c5adf9a0e884c5eb5186ed33137badcbbb0560b86e910a2f1e76", 1060 | "sha256:3d0a409d3b28607cc427aa5c30a6f1e4452cc44e311f843e05edb28ab5e36da0", 1061 | "sha256:44e58a0554b21fc662f2712814a746635ed668d0fbc98b7cb9d74cb798d202e6", 1062 | "sha256:458dea649f2f02a0b244ae6aef8dc29325a2810aa26b07af8374dc2a9faf57e3", 1063 | "sha256:48e466162a24daf86f6b5ca72444d2bf39a5e58da5f96370078be67c67adc978", 1064 | "sha256:49d238cf4b69652257db66d0c623cd3e09b5d2e9576b56bc067a396133a00d4a", 1065 | "sha256:4ca1ed0bb2d850aa8471387882247c68f1e62a4af0ce9c8a1dbe0d2bf69e41fb", 1066 | "sha256:52533489f28d62eb1258a965f2aba28a82aa747202c8fa5a1c7a43b5db0e85c1", 1067 | "sha256:548d6482dc8aadbe7e79d1b5806585c8120bafa1ef841167bc9090522b610fa6", 1068 | "sha256:5619f3f5a4db5dbb572b095ea3cb5cc035335159d9da950830c9c4db2fbb6995", 1069 | "sha256:57459b68e5cd85b0be8184382cefd91959cafe79ae019e6b1ae6e2ba8a12cda7", 1070 | "sha256:5a34d2395073ef862b4032343cf0c32a712f3ab49d7ec4f42c9661e0294d106f", 1071 | "sha256:61706a6b6c24bdece85ff177fec393545a3191eeda35b07aaa1458a027ad1304", 1072 | "sha256:724c292bb26365659fc434e9567b3f1adbdb5e8d640c936ed901f49e03e5d32e", 1073 | "sha256:73461eed88a88c866656e08f89299720a38cb4e9d34ae6bf5df6f71102570f2e", 1074 | "sha256:76705c9325d72a81155bb6ab48d4312e0032bf045fb0754889133200f7a0d849", 1075 | "sha256:76c1c8efb3ca3a1818b837aea423ff8a07bbf7aafe9f2f6582b61a0458b1a329", 1076 | "sha256:77a41c26205d2353a4c94d02be51d6cbdf63c06fbc1295ea57dad7e2d3381b71", 1077 | "sha256:79986f3b4af059777111409ee517da24a529bdbd46da578b33f25580adcff728", 1078 | "sha256:7cff25c5b315e63b07a36f0c2bab32c58eafbe57d0dce61b614ef4c76058c115", 1079 | "sha256:7f7e58effd14b641c5e4dec8c7dab02fb67a13df90329e61c869b9cc607ef752", 1080 | "sha256:820c4a08195a681252f46926de10e29b6bbf3e17b30037bd4250d72dd3ddaab8", 1081 | "sha256:87e34f31ca8f168c56d6fbf99692cc8d3b445abb5bfd08c229ae992d7547a92a", 1082 | "sha256:8f03d3f0d01cb5a018debeb412441996a517b11c5c17ab2001aa0597c6d6882c", 1083 | "sha256:90f26dc6d5f241ba358bef79be9ce06de58d477ca8485e3291675436d3827cf8", 1084 | "sha256:955215ed0604dac5b01907424dfa28b40f2b2292d6493445dd34d0dfa72586a8", 1085 | "sha256:985bbb1316192b98f32e25e7b9958088431d853ac63aca1d2c236f40afb17c83", 1086 | "sha256:a382372898a07479bd34bda781008e4a954ed8750f17891e794521c3e21c2e1c", 1087 | "sha256:a882ac0a351288dd18ecae3326b8a49d10c61a68b01419f3a0b9a306190baf69", 1088 | "sha256:aa8d6cdc8b8aa19ceb319aaa2b660cdaccc533ec477eeb1309e2a291eaacc43a", 1089 | "sha256:abc719161780932c4e11aaebb203be3d6acc6b38d2f26c0f523b5b59d2fc1996", 1090 | "sha256:abf34e43c531bbb510ae7e8f5b2b1f2a8ab93219510e2b287a944432fad135f3", 1091 | "sha256:ade6d25bb29c4555d718ac6d1443a7386595528c33d6b133b258f65f963bb0f6", 1092 | "sha256:afea96f64efa98df4da6958bae37f1cbea7932c35878b185e5982821bc883369", 1093 | "sha256:b1579413ae492b05de5a6174574f8c44c2b9b122a42015c5292afa4be2507f28", 1094 | "sha256:b3451108ab861040754fa5208bca4a5496c65875710f76789a9ad27c801a0075", 1095 | "sha256:b9af3757495c1ee3b5c4e945c1df7be95562277c6e5bccc20a39aec50f826cd0", 1096 | "sha256:bc16ac425cc927d0a57d242589f87ee093884ea4804c05a13834d07c20db203c", 1097 | "sha256:c2910967e6ab16bf6fbeb1f771c89a7050947221ae12a5b0b60f3bca2ee19bca", 1098 | "sha256:c2b92812bd214018e50b6380ea3ac0c8bb01ac07fcc14c5f86a5bb25e74026e9", 1099 | "sha256:c2f20ce161ebdb0091a10c9ca0372e023ce24980d0e1f810f519da6f79c60800", 1100 | "sha256:c56d748ea50215abef7030c72b60dd723ed5b5c7e65e7bc2504e77843631c1a6", 1101 | "sha256:c7c133e93b405eb0d36fa430c94185bdd13c36204a8635470cccc200723c13bb", 1102 | "sha256:c9c6c9b2c2f80747a98f34ef491c4d7b1a8d4853937bb1492774992a120f475d", 1103 | "sha256:cbc8df5c6a88ba5ae385d8930da02201165408dde8d8322072e3e5ddd4f68e22", 1104 | "sha256:cff084c6933680d1f8b2f3b4ff5bbb88538a4aac00d199ac13f49d0698727ecb", 1105 | "sha256:d2045d6d9439a0078f2a34b57c7b18c4a6aef0bee37f22e4ec9f32456c852c71", 1106 | "sha256:d20a0ddb3e989e8807d83225a27e5c2eb2260eaa851532086e9e0fa0d5287d83", 1107 | "sha256:d457aed310f2670f59cc5b57dcfced452aeeed77f9da2b9763616bd57e4dbaae", 1108 | "sha256:d89528b4943d27029a2818f847c10c2cecc79fa9590f3cb1860459a5be7933eb", 1109 | "sha256:db0b2af416ba735c6304c47f75d348f498b92952f5e3e8bff449336d2728795d", 1110 | "sha256:deee9ca4727f53464daf089536e68b13e6104e84a37820a88b0a057b97bba2d2", 1111 | "sha256:df27ffddff4190667d40de7beba4a950b5ce78fe28a7dcc41d6f8a700a80a3c0", 1112 | "sha256:e0c95ddd4f6e9fca4e9e3afaa4f9df8552f0ba5d1004e89ef0a68e1f1f9807c7", 1113 | "sha256:e1c1be77bc5fb77d923850f82e55a928f8638f64a61f00ff18a67c7404faf008", 1114 | "sha256:e1ffa1c924e8c72778b9ccd386a7067cddf626884fd8277f503c48bb5f51c762", 1115 | "sha256:e2400a94f7dd9cb20cd012951a0cbf8249e3d554c63a9c0cdfd5cbb6c01d2dec", 1116 | "sha256:e61f091c3ba0c3578411ef505992d356a812fb200643eab27f4f70eed34a29ef", 1117 | "sha256:e8a701123029cc240cea61dd2d16ad57cab4691804143ce80ecd9286b464d180", 1118 | "sha256:eadbefd5e92ef8a345f0525b5cfd01cf4e4cc651a2cffb8f23c0dd184975d787", 1119 | "sha256:f32260e556a983bc5c7ed588d04c942c9a8f9c2e99213fec11a031e316874c7e", 1120 | "sha256:f8115e303280ba09f3898194791a153862cbf9eef722ad8f7f741987ee2a97c7", 1121 | "sha256:fedbdc753827cf014c01dbbee9c3be17e5a208dcd1bf8641ce2cd29580d1f0d4" 1122 | ], 1123 | "markers": "python_version >= '3.6'", 1124 | "version": "==25.1.1" 1125 | }, 1126 | "six": { 1127 | "hashes": [ 1128 | "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", 1129 | "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" 1130 | ], 1131 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 1132 | "version": "==1.16.0" 1133 | }, 1134 | "stack-data": { 1135 | "hashes": [ 1136 | "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", 1137 | "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695" 1138 | ], 1139 | "version": "==0.6.3" 1140 | }, 1141 | "tornado": { 1142 | "hashes": [ 1143 | "sha256:1bd19ca6c16882e4d37368e0152f99c099bad93e0950ce55e71daed74045908f", 1144 | "sha256:22d3c2fa10b5793da13c807e6fc38ff49a4f6e1e3868b0a6f4164768bb8e20f5", 1145 | "sha256:502fba735c84450974fec147340016ad928d29f1e91f49be168c0a4c18181e1d", 1146 | "sha256:65ceca9500383fbdf33a98c0087cb975b2ef3bfb874cb35b8de8740cf7f41bd3", 1147 | "sha256:71a8db65160a3c55d61839b7302a9a400074c9c753040455494e2af74e2501f2", 1148 | "sha256:7ac51f42808cca9b3613f51ffe2a965c8525cb1b00b7b2d56828b8045354f76a", 1149 | "sha256:7d01abc57ea0dbb51ddfed477dfe22719d376119844e33c661d873bf9c0e4a16", 1150 | "sha256:805d507b1f588320c26f7f097108eb4023bbaa984d63176d1652e184ba24270a", 1151 | "sha256:9dc4444c0defcd3929d5c1eb5706cbe1b116e762ff3e0deca8b715d14bf6ec17", 1152 | "sha256:ceb917a50cd35882b57600709dd5421a418c29ddc852da8bcdab1f0db33406b0", 1153 | "sha256:e7d8db41c0181c80d76c982aacc442c0783a2c54d6400fe028954201a2e032fe" 1154 | ], 1155 | "markers": "python_version >= '3.8'", 1156 | "version": "==6.3.3" 1157 | }, 1158 | "traitlets": { 1159 | "hashes": [ 1160 | "sha256:7564b5bf8d38c40fa45498072bf4dc5e8346eb087bbf1e2ae2d8774f6a0f078e", 1161 | "sha256:98277f247f18b2c5cabaf4af369187754f4fb0e85911d473f72329db8a7f4fae" 1162 | ], 1163 | "markers": "python_version >= '3.8'", 1164 | "version": "==5.11.2" 1165 | }, 1166 | "wcwidth": { 1167 | "hashes": [ 1168 | "sha256:77f719e01648ed600dfa5402c347481c0992263b81a027344f3e1ba25493a704", 1169 | "sha256:8705c569999ffbb4f6a87c6d1b80f324bd6db952f5eb0b95bc07517f4c1813d4" 1170 | ], 1171 | "version": "==0.2.8" 1172 | } 1173 | } 1174 | } 1175 | --------------------------------------------------------------------------------