├── 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 |
--------------------------------------------------------------------------------