From 1c2c9688f5d15550028246682da2dafca332c838 Mon Sep 17 00:00:00 2001 From: Maki Date: Sun, 9 Jun 2024 10:14:12 +0900 Subject: [PATCH 01/10] =?UTF-8?q?=F0=9F=93=9D=20[chore]=20=E4=B8=8D?= =?UTF-8?q?=E8=A6=81=E3=81=AA=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB=E3=82=92?= =?UTF-8?q?=E9=99=A4=E5=A4=96=E3=83=AA=E3=82=B9=E3=83=88=E3=81=AB=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - .harmon_ai, pegasus_surf.egg-info, .aira を .SourceSageignore に追加 --- .SourceSageignore | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.SourceSageignore b/.SourceSageignore index e29d512..703df65 100644 --- a/.SourceSageignore +++ b/.SourceSageignore @@ -35,4 +35,8 @@ tests template aira.egg-info aira.Gaiah.md -README_template.md \ No newline at end of file +README_template.md +output +.harmon_ai +pegasus_surf.egg-info +.aira \ No newline at end of file From 84fcd7d914e723127b5118752fb1fe02c779145d Mon Sep 17 00:00:00 2001 From: Maki Date: Sun, 9 Jun 2024 10:14:15 +0900 Subject: [PATCH 02/10] =?UTF-8?q?=F0=9F=93=9D=20[chore]=20=E4=B8=8D?= =?UTF-8?q?=E8=A6=81=E3=81=AA=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB=E3=81=A8?= =?UTF-8?q?=E3=83=87=E3=82=A3=E3=83=AC=E3=82=AF=E3=83=88=E3=83=AA=E3=82=92?= =?UTF-8?q?=E9=99=A4=E5=A4=96=E3=83=AA=E3=82=B9=E3=83=88=E3=81=AB=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - output ディレクトリと urls.txt ファイルを .gitignore に追加 --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index cfb8f92..a9edce7 100644 --- a/.gitignore +++ b/.gitignore @@ -167,4 +167,5 @@ tmp2.md .SourceSageAssets .aira/aira.Gaiah.md .harmon_ai/README_template.md -output \ No newline at end of file +output +urls.txt From 6da2e348c629408cffe141fdb8bbfa112c6d56da Mon Sep 17 00:00:00 2001 From: Maki Date: Sun, 9 Jun 2024 10:14:18 +0900 Subject: [PATCH 03/10] =?UTF-8?q?=E2=9C=A8=20[feat]=20Pegasus=E3=81=AE?= =?UTF-8?q?=E4=BD=BF=E3=81=84=E6=96=B9=E3=82=92=E8=A9=B3=E7=B4=B0=E5=8C=96?= =?UTF-8?q?=E3=80=81LLM=E3=81=AB=E3=82=88=E3=82=8B=E3=83=95=E3=82=A3?= =?UTF-8?q?=E3=83=AB=E3=82=BF=E3=83=AA=E3=83=B3=E3=82=B0=E6=A9=9F=E8=83=BD?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Pegasus のインストール方法を `pegasus` から `pegasus-surf` に変更 - コマンドライン引数 `--base-url` の代わりに `--url-file` を追加し、URLリストからのスクレイピングを可能に - `--max-depth` 引数を追加し、クロール深度を制限可能に - LLM を使用したコンテンツフィルタリング機能を追加し、その説明と使用方法を追記 --- README.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e06eb61..d3b3589 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ pegasus は、ウェブサイトを再帰的にクロールし、そのコンテ pip を使用して pegasus をインストールします。 ```shell -pip install pegasus +pip install pegasus-surf ``` ## 使い方 @@ -53,7 +53,18 @@ pegasus をコマンドラインから使用するには、以下のようなコ ```shell pegasus https://example.com/start-page output_directory --exclude-selectors header footer nav --include-domain example.com --exclude-keywords login --output-extension txt -pegasus https://docs.eraser.io/docs/what-is-eraser output/eraser_docs --exclude-selectors header footer nav aside .sidebar .header .footer .navigation .breadcrumbs --include-domain docs.eraser.io --exclude-keywords login --output-extension .txt + +pegasus --base-url https://docs.eraser.io/docs/what-is-eraser output/eraser_docs --exclude-selectors header footer nav aside .sidebar .header .footer .navigation .breadcrumbs --include-domain docs.eraser.io --exclude-keywords login --output-extension .txt + +# 深度を指定して実行 +pegasus --base-url https://docs.eraser.io/docs/what-is-eraser output/eraser_docs2 --exclude-selectors header footer nav aside .sidebar .header .footer .navigation .breadcrumbs --include-domain docs.eraser.io --exclude-keywords login --output-extension .txt --max-depth 2 + +# URLが記載されたテキストファイルを指定して実行 +pegasus --url-file urls.txt output/roomba --exclude-selectors header footer nav aside .sidebar .header .footer .navigation .breadcrumbs --exclude-keywords login --output-extension .txt --max-depth 1 + +# LLMを使った仕分け +pegasus --url-file urls.txt output/roomba2 --exclude-selectors header footer nav aside .sidebar .header .footer .navigation .breadcrumbs --exclude-keywords login --output-extension .txt --max-depth 1 --system-message "あなたは、与えられたウェブサイトのコンテンツが特定のトピックに関連する有用な情報を含んでいるかどうかを判断するアシスタントです。トピックに関連する有益な情報が含まれている場合は「True」、そうでない場合は「False」と回答してください。" --classification-prompt "次のウェブサイトのコンテンツは、Roomba APIやiRobotに関する有益な情報を提供していますか? 提供している場合は「True」、そうでない場合は「False」と回答してください。" + ``` - `https://example.com/start-page`: クロールを開始するベース URL を指定します。 From a2a5bd9391f5d73b27606ee323b5545fe8506e94 Mon Sep 17 00:00:00 2001 From: Maki Date: Sun, 9 Jun 2024 10:14:21 +0900 Subject: [PATCH 04/10] =?UTF-8?q?=E2=9C=A8=20[feat]=20Pegasus=E3=81=AE?= =?UTF-8?q?=E6=A9=9F=E8=83=BD=E3=82=92=E5=A4=A7=E5=B9=85=E3=81=AB=E6=8B=A1?= =?UTF-8?q?=E5=BC=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - LLMによるコンテンツフィルタリング機能を追加 - クロール深度を制限する `max_depth` 引数を追加 - ドメインごとのサマリーファイルを作成する機能を追加 - 不要なサイトをフィルタリングし、除外フォルダに保存する機能を追加 - その他、リトライ処理、エラーハンドリング、ログ出力などを改善 --- pegasus/Pegasus.py | 224 ++++++++++++++++++++++++++++++--------------- 1 file changed, 149 insertions(+), 75 deletions(-) diff --git a/pegasus/Pegasus.py b/pegasus/Pegasus.py index f079eda..f62fa6b 100644 --- a/pegasus/Pegasus.py +++ b/pegasus/Pegasus.py @@ -1,4 +1,3 @@ -# pegasus/pegasus.py import requests import markdownify from bs4 import BeautifulSoup @@ -6,82 +5,157 @@ import os import re import loguru +import time from art import * +from litellm import completion +from tqdm import tqdm +import litellm +# litellm.set_verbose=True logger = loguru.logger class Pegasus: - def __init__(self, base_url, output_dir, exclude_selectors=None, include_domain=None, exclude_keywords=None, output_extension=".md", dust_size=1000): - self.base_url = base_url - self.output_dir = output_dir - self.exclude_selectors = exclude_selectors - self.include_domain = include_domain - self.exclude_keywords = exclude_keywords - self.visited_urls = set() - self.output_extension = output_extension - self.dust_size = dust_size - tprint(" Pegasus ", font="rnd-xlarge") - logger.info("初期化パラメータ:") - logger.info(f" base_url: {base_url}") - logger.info(f" output_dir: {output_dir}") - logger.info(f" exclude_selectors: {exclude_selectors}") - logger.info(f" include_domain: {include_domain}") - logger.info(f" exclude_keywords: {exclude_keywords}") - logger.info(f" output_extension: {output_extension}") - logger.info(f" dust_size: {dust_size}") - - def download_and_convert(self, url): - os.makedirs(self.output_dir, exist_ok=True) - if url in self.visited_urls: - return - self.visited_urls.add(url) - - try: - response = requests.get(url) - response.raise_for_status() - - soup = BeautifulSoup(response.text, 'html.parser') - - if self.exclude_selectors: - for selector in self.exclude_selectors: - for element in soup.select(selector): - element.decompose() - - markdown_content = markdownify.markdownify(str(soup)) - markdown_content = re.sub(r'\n{5,}', '\n\n\n\n', markdown_content) - - parsed_url = urlparse(url) - output_file = f"{self.output_dir}/{parsed_url.path.replace('/', '_')}{self.output_extension}" - - if len(markdown_content) < self.dust_size: - dust_dir = os.path.join(self.output_dir, "dust") - os.makedirs(dust_dir, exist_ok=True) - output_file = f"{dust_dir}/{parsed_url.path.replace('/', '_')}{self.output_extension}" - - with open(output_file, 'w', encoding='utf-8') as file: - file.write(markdown_content) - - logger.info(f"変換成功: {url} ---> {output_file} [{len(markdown_content)/1000}kb]") - - soup_url = BeautifulSoup(response.text, 'html.parser') - - for link in soup_url.find_all('a'): - href = link.get('href') - if href: - absolute_url = urljoin(url, href) - if self.include_domain and self.include_domain in absolute_url: - if self.exclude_keywords: - if any(keyword in absolute_url for keyword in self.exclude_keywords): - continue - absolute_url = absolute_url.split('#')[0] - self.download_and_convert(absolute_url) - - except requests.exceptions.RequestException as e: - logger.error(f"ダウンロードエラー: {url}: {e}") - except IOError as e: - logger.error(f"書き込みエラー: {output_file}: {e}") - - def run(self): - logger.info(f"スクレイピング開始: base_url={self.base_url}") - self.download_and_convert(self.base_url) - logger.info("スクレイピング完了") \ No newline at end of file + def __init__(self, output_dir, exclude_selectors=None, include_domain=None, exclude_keywords=None, output_extension=".md", dust_size=1000, max_depth=None, system_message=None, classification_prompt=None, max_retries=3): + self.output_dir = output_dir + self.exclude_selectors = exclude_selectors + self.include_domain = include_domain + self.exclude_keywords = exclude_keywords + self.visited_urls = set() + self.output_extension = output_extension + self.dust_size = dust_size + self.max_depth = max_depth + self.domain_summaries = {} + self.system_message = system_message + self.classification_prompt = classification_prompt + self.max_retries = max_retries + tprint(" Pegasus ", font="rnd-xlarge") + logger.info("初期化パラメータ:") + logger.info(f" output_dir: {output_dir}") + logger.info(f" exclude_selectors: {exclude_selectors}") + logger.info(f" include_domain: {include_domain}") + logger.info(f" exclude_keywords: {exclude_keywords}") + logger.info(f" output_extension: {output_extension}") + logger.info(f" dust_size: {dust_size}") + logger.info(f" max_depth: {max_depth}") + logger.info(f" system_message: {system_message}") + logger.info(f" classification_prompt: {classification_prompt}") + logger.info(f" max_retries: {max_retries}") + + def filter_site(self, markdown_content): + if(self.classification_prompt is None): + return True + + retry_count = 0 + while retry_count < self.max_retries: + try: + messages = [ + {"role": "system", "content": self.system_message}, + {"role": "user", "content": f"{self.classification_prompt}\n\n{markdown_content}"} + ] + response = completion( + model="gemini/gemini-1.5-pro-latest", + messages=messages + ) + content = response.get('choices', [{}])[0].get('message', {}).get('content') + logger.debug(f"content : {content}") + if "true" in content.lower(): + return True + elif "false" in content.lower(): + return False + else: + raise ValueError("分類結果が曖昧です。") + except Exception as e: + retry_count += 1 + logger.warning(f"フィルタリングでエラーが発生しました。リトライします。({retry_count}/{self.max_retries})\nError: {e}") + + if "429" in str(e): + sleep_time = 60 # 60秒スリープ + else: + sleep_time = 10 # その他のエラーの場合は10秒スリープ + + for _ in tqdm(range(sleep_time), desc="Sleeping", unit="s"): + time.sleep(1) + + logger.error(f"フィルタリングに失敗しました。リトライ回数の上限に達しました。({self.max_retries}回)") + return True + + def download_and_convert(self, url, depth=0): + if url in self.visited_urls: + return + self.visited_urls.add(url) + + try: + response = requests.get(url) + response.raise_for_status() + + soup = BeautifulSoup(response.text, 'html.parser') + + if self.exclude_selectors: + for selector in self.exclude_selectors: + for element in soup.select(selector): + element.decompose() + + markdown_content = markdownify.markdownify(str(soup)) + markdown_content = re.sub(r'\n{5,}', '\n\n\n\n', markdown_content) + + if not self.filter_site(markdown_content): + parsed_url = urlparse(url) + domain = parsed_url.netloc + domain_dir = os.path.join(self.output_dir, domain) + os.makedirs(domain_dir, exist_ok=True) + excluded_dir = os.path.join(domain_dir, "excluded") + os.makedirs(excluded_dir, exist_ok=True) + output_file = f"{excluded_dir}/{parsed_url.path.replace('/', '_')}{self.output_extension}" + else: + parsed_url = urlparse(url) + domain = parsed_url.netloc + domain_dir = os.path.join(self.output_dir, domain) + os.makedirs(domain_dir, exist_ok=True) + + output_file = f"{domain_dir}/{parsed_url.path.replace('/', '_')}{self.output_extension}" + + if len(markdown_content) < self.dust_size: + dust_dir = os.path.join(domain_dir, "dust") + os.makedirs(dust_dir, exist_ok=True) + output_file = f"{dust_dir}/{parsed_url.path.replace('/', '_')}{self.output_extension}" + + with open(output_file, 'w', encoding='utf-8') as file: + file.write(markdown_content) + + logger.info(f"[{depth}]変換成功: {url} ---> {output_file} [{len(markdown_content)/1000}kb]") + + if domain not in self.domain_summaries: + self.domain_summaries[domain] = [] + self.domain_summaries[domain].append(f"# {os.path.basename(output_file)}\n\n---\n\n{markdown_content}") + + if self.max_depth is None or depth < self.max_depth: + soup_url = BeautifulSoup(response.text, 'html.parser') + + for link in soup_url.find_all('a'): + href = link.get('href') + if href: + absolute_url = urljoin(url, href) + if (self.include_domain and self.include_domain in absolute_url) or (self.include_domain == ""): + if self.exclude_keywords: + if any(keyword in absolute_url for keyword in self.exclude_keywords): + continue + absolute_url = absolute_url.split('#')[0] + self.download_and_convert(absolute_url, depth + 1) + + except requests.exceptions.RequestException as e: + logger.error(f"ダウンロードエラー: {url}: {e}") + except IOError as e: + logger.error(f"書き込みエラー: {output_file}: {e}") + + def create_domain_summaries(self): + for domain, summaries in self.domain_summaries.items(): + summary_file = os.path.join(self.output_dir, f"{domain}_summary{self.output_extension}") + with open(summary_file, 'w', encoding='utf-8') as file: + file.write('\n\n'.join(summaries)) + logger.info(f"サマリーファイル作成: {summary_file}") + + def run(self, base_url): + logger.info(f"スクレイピング開始: base_url={base_url}") + self.download_and_convert(base_url) + self.create_domain_summaries() + logger.info("スクレイピング完了") \ No newline at end of file From db755b705e1c1b51f07270c1dee41914a2c5a0e0 Mon Sep 17 00:00:00 2001 From: Maki Date: Sun, 9 Jun 2024 10:14:24 +0900 Subject: [PATCH 05/10] =?UTF-8?q?=E2=9C=A8=20[feat]=20Pegasus=E3=81=AE?= =?UTF-8?q?=E3=82=B3=E3=83=9E=E3=83=B3=E3=83=89=E3=83=A9=E3=82=A4=E3=83=B3?= =?UTF-8?q?=E5=BC=95=E6=95=B0=E3=82=92=E6=8B=A1=E5=BC=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `--base-url` の代わりに `--url-file` を追加し、URLリストからのスクレイピングを可能に - `--max-depth` 引数を追加し、クロール深度を制限可能に - LLM を使用したコンテンツフィルタリングのための引数 `--system-message` と `--classification-prompt` を追加 - その他、リトライ処理、エラーハンドリング、ログ出力などを改善 --- pegasus/cli.py | 40 +++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/pegasus/cli.py b/pegasus/cli.py index 5a10c42..1c41460 100644 --- a/pegasus/cli.py +++ b/pegasus/cli.py @@ -1,29 +1,47 @@ -# pegasus/cli.py import argparse from .Pegasus import Pegasus +from dotenv import load_dotenv +load_dotenv(verbose=True) def main(): parser = argparse.ArgumentParser(description='Pegasus') - parser.add_argument('base_url', help='Base URL to start scraping') - parser.add_argument('output_dir', help='Output directory for markdown files') - parser.add_argument('--exclude-selectors', nargs='+', help='CSS selectors to exclude') - parser.add_argument('--include-domain', help='Domain to include in URL matching') - parser.add_argument('--exclude-keywords', nargs='+', help='Keywords to exclude in URL matching') - parser.add_argument('--output-extension', default='.md', help='Output file extension (default: .md)') - parser.add_argument('--dust-size', type=int, default=1000, help='File size threshold for moving to dust folder (default: 1000 bytes)') + parser.add_argument('--base-url', help='スクレイピングを開始するベースURL') + parser.add_argument('--url-file', help='スクレイピングするURLが記載されたテキストファイル') + parser.add_argument('output_dir', help='Markdownファイルの出力ディレクトリ') + parser.add_argument('--exclude-selectors', nargs='+', help='除外するCSSセレクター') + parser.add_argument('--include-domain', default='', help='URLマッチングに含めるドメイン') + parser.add_argument('--exclude-keywords', nargs='+', help='URLマッチングから除外するキーワード') + parser.add_argument('--output-extension', default='.md', help='出力ファイルの拡張子 (デフォルト: .md)') + parser.add_argument('--dust-size', type=int, default=1000, help='ダストフォルダに移動するファイルサイズのしきい値 (デフォルト: 1000バイト)') + parser.add_argument('--max-depth', type=int, default=None, help='再帰処理の最大深度 (デフォルト: 制限なし)') + parser.add_argument('--system-message', default=None, help='LiteLLMのシステムメッセージ(サイトの分類に使用)') + parser.add_argument('--classification-prompt', default=None, help='LiteLLMのサイト分類プロンプト(TrueまたはFalseを返すようにしてください)') + parser.add_argument('--max-retries', type=int, default=3, help='フィルタリングのリトライ回数の上限(デフォルト:3)') args = parser.parse_args() pegasus = Pegasus( - base_url=args.base_url, output_dir=args.output_dir, exclude_selectors=args.exclude_selectors, include_domain=args.include_domain, exclude_keywords=args.exclude_keywords, output_extension=args.output_extension, - dust_size=args.dust_size + dust_size=args.dust_size, + max_depth=args.max_depth, + system_message=args.system_message, + classification_prompt=args.classification_prompt, + max_retries=args.max_retries ) - pegasus.run() + + if args.base_url: + pegasus.run(args.base_url) + elif args.url_file: + with open(args.url_file, 'r') as file: + urls = file.read().splitlines() + for url in urls: + pegasus.run(url) + else: + parser.error("--base-url または --url-file のいずれかを指定してください。") if __name__ == '__main__': main() \ No newline at end of file From 67365b41c7fa72f5b04063bb1e8c1c449d6e7ea0 Mon Sep 17 00:00:00 2001 From: Maki Date: Sun, 9 Jun 2024 10:14:27 +0900 Subject: [PATCH 06/10] =?UTF-8?q?=F0=9F=93=A6=20[chore]=20=E4=BE=9D?= =?UTF-8?q?=E5=AD=98=E9=96=A2=E4=BF=82=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - LLMを使用したコンテンツフィルタリングに必要なライブラリ `litellm`, `python-dotenv`, `google-generativeai` を追加 --- setup.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c41dfe7..ad575cb 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name='pegasus-surf', - version='0.1.1', + version='0.2.0', description='A package for scraping websites and converting them to Markdown', author='Maki', author_email='sunwood.ai.labs@gmail.com', @@ -20,6 +20,9 @@ 'beautifulsoup4', 'loguru', 'art', + 'litellm', + 'python-dotenv', + 'google-generativeai', ], entry_points={ 'console_scripts': [ From 2a084199627a0588f14c11bc0f5365bc4d9ff574 Mon Sep 17 00:00:00 2001 From: Maki Date: Sun, 9 Jun 2024 10:14:30 +0900 Subject: [PATCH 07/10] =?UTF-8?q?=E2=9C=A8=20[feat]=20=E3=82=B9=E3=82=AF?= =?UTF-8?q?=E3=83=AC=E3=82=A4=E3=83=94=E3=83=B3=E3=82=B0=E5=AF=BE=E8=B1=A1?= =?UTF-8?q?URL=E3=81=AE=E3=82=B5=E3=83=B3=E3=83=97=E3=83=AB=E3=83=95?= =?UTF-8?q?=E3=82=A1=E3=82=A4=E3=83=AB=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Roombaに関する情報を収集するためのサンプルURLリストを追加 --- urls.example.txt | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 urls.example.txt diff --git a/urls.example.txt b/urls.example.txt new file mode 100644 index 0000000..c74a08c --- /dev/null +++ b/urls.example.txt @@ -0,0 +1,8 @@ +https://iroboteducation.github.io/create3_docs/ +https://raw.githubusercontent.com/koalazak/dorita980/master/README.md +https://raw.githubusercontent.com/AtsushiSakai/PyRoombaAdapter/master/README.md +https://qiita.com/Yurix/items/234f7775a1f9d3ad43af +https://raw.githubusercontent.com/docofab/RoombaControlls/main/README.md +https://blog.sikmi.com/slack-bot-roomba +https://qiita.com/oystaar/items/d0a013facd02e8d81479 +https://kakaku.com/kaden/vacuum-cleaner/itemlist.aspx?pdf_se=10 \ No newline at end of file From c3ee74a080069d3aad9369ad2b164c1856ae903d Mon Sep 17 00:00:00 2001 From: Maki Date: Sun, 9 Jun 2024 10:22:17 +0900 Subject: [PATCH 08/10] =?UTF-8?q?=E2=9C=A8=20[feat]=20Pegasus=E3=81=AE?= =?UTF-8?q?=E3=83=95=E3=82=A3=E3=83=AB=E3=82=BF=E3=83=AA=E3=83=B3=E3=82=B0?= =?UTF-8?q?=E6=A9=9F=E8=83=BD=E3=82=92=E5=BC=B7=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - LLMモデル、レート制限エラー時、その他のエラー時のスリープ時間を引数で指定可能に - 初期化パラメータに`model`、`rate_limit_sleep`、`other_error_sleep` を追加 - `filter_site`メソッドで使用するLLMモデルを`self.model`から取得するように変更 - レート制限エラー時とその他のエラー時のスリープ時間をそれぞれ`self.rate_limit_sleep`と`self.other_error_sleep`から取得するように変更 --- pegasus/Pegasus.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/pegasus/Pegasus.py b/pegasus/Pegasus.py index f62fa6b..8d2a816 100644 --- a/pegasus/Pegasus.py +++ b/pegasus/Pegasus.py @@ -15,7 +15,9 @@ logger = loguru.logger class Pegasus: - def __init__(self, output_dir, exclude_selectors=None, include_domain=None, exclude_keywords=None, output_extension=".md", dust_size=1000, max_depth=None, system_message=None, classification_prompt=None, max_retries=3): + def __init__(self, output_dir, exclude_selectors=None, include_domain=None, exclude_keywords=None, output_extension=".md", + dust_size=1000, max_depth=None, system_message=None, classification_prompt=None, max_retries=3, + model='gemini/gemini-1.5-pro-latest', rate_limit_sleep=60, other_error_sleep=10): self.output_dir = output_dir self.exclude_selectors = exclude_selectors self.include_domain = include_domain @@ -28,6 +30,9 @@ def __init__(self, output_dir, exclude_selectors=None, include_domain=None, excl self.system_message = system_message self.classification_prompt = classification_prompt self.max_retries = max_retries + self.model = model + self.rate_limit_sleep = rate_limit_sleep + self.other_error_sleep = other_error_sleep tprint(" Pegasus ", font="rnd-xlarge") logger.info("初期化パラメータ:") logger.info(f" output_dir: {output_dir}") @@ -40,6 +45,9 @@ def __init__(self, output_dir, exclude_selectors=None, include_domain=None, excl logger.info(f" system_message: {system_message}") logger.info(f" classification_prompt: {classification_prompt}") logger.info(f" max_retries: {max_retries}") + logger.info(f" model: {model}") + logger.info(f" rate_limit_sleep: {rate_limit_sleep}") + logger.info(f" other_error_sleep: {other_error_sleep}") def filter_site(self, markdown_content): if(self.classification_prompt is None): @@ -69,9 +77,9 @@ def filter_site(self, markdown_content): logger.warning(f"フィルタリングでエラーが発生しました。リトライします。({retry_count}/{self.max_retries})\nError: {e}") if "429" in str(e): - sleep_time = 60 # 60秒スリープ + sleep_time = self.rate_limit_sleep # レート制限エラー時のスリープ時間をself.rate_limit_sleepから取得 else: - sleep_time = 10 # その他のエラーの場合は10秒スリープ + sleep_time = self.other_error_sleep # その他のエラー時のスリープ時間をself.other_error_sleepから取得 for _ in tqdm(range(sleep_time), desc="Sleeping", unit="s"): time.sleep(1) From 5d972a32a765485a6ef1eecfff92d4c20ab4db5e Mon Sep 17 00:00:00 2001 From: Maki Date: Sun, 9 Jun 2024 10:22:20 +0900 Subject: [PATCH 09/10] =?UTF-8?q?=E2=9C=A8=20[feat]=20Pegasus=E3=81=AE?= =?UTF-8?q?=E3=82=B3=E3=83=9E=E3=83=B3=E3=83=89=E3=83=A9=E3=82=A4=E3=83=B3?= =?UTF-8?q?=E5=BC=95=E6=95=B0=E3=82=92=E6=8B=A1=E5=BC=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - LLMモデル、レート制限エラー時、その他のエラー時のスリープ時間をコマンドライン引数で指定可能に - `--model`、`--rate-limit-sleep`、`--other-error-sleep`引数を追加 --- pegasus/cli.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pegasus/cli.py b/pegasus/cli.py index 1c41460..3967257 100644 --- a/pegasus/cli.py +++ b/pegasus/cli.py @@ -17,7 +17,10 @@ def main(): parser.add_argument('--system-message', default=None, help='LiteLLMのシステムメッセージ(サイトの分類に使用)') parser.add_argument('--classification-prompt', default=None, help='LiteLLMのサイト分類プロンプト(TrueまたはFalseを返すようにしてください)') parser.add_argument('--max-retries', type=int, default=3, help='フィルタリングのリトライ回数の上限(デフォルト:3)') - + parser.add_argument('--model', default='gemini/gemini-1.5-pro-latest', help='LiteLLMのモデル名 (デフォルト: gemini/gemini-1.5-pro-latest)') + parser.add_argument('--rate-limit-sleep', type=int, default=60, help='レート制限エラー時のスリープ時間(秒) (デフォルト: 60)') + parser.add_argument('--other-error-sleep', type=int, default=10, help='その他のエラー時のスリープ時間(秒) (デフォルト: 10)') + args = parser.parse_args() pegasus = Pegasus( From bb9c7fe77be69e80d74fc277f5367d6ea4de6dbc Mon Sep 17 00:00:00 2001 From: Maki Date: Sun, 9 Jun 2024 10:29:13 +0900 Subject: [PATCH 10/10] =?UTF-8?q?=F0=9F=93=9A=20[docs]=20README.md=20?= =?UTF-8?q?=E3=81=AE=E6=83=85=E5=A0=B1=E3=82=92=E6=95=B4=E7=90=86=E3=80=81?= =?UTF-8?q?=E6=9C=80=E6=96=B0=E5=8C=96=E3=81=97=E3=80=81=E3=82=88=E3=82=8A?= =?UTF-8?q?=E5=88=86=E3=81=8B=E3=82=8A=E3=82=84=E3=81=99=E3=81=8F=E6=94=B9?= =?UTF-8?q?=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Pegasus のインストールコマンドを修正し、pip install pegasus-surf を明記 - コマンドライン引数、Python スクリプトからの使用方法をそれぞれ章立てし、より見やすく整理 - 各引数の説明を詳細化し、デフォルト値やオプションの情報を追加 - LLM を使ったサイト分類機能について、より具体的に説明 - 使用例を拡充し、コマンドライン引数と Python スクリプトからの使用方法を両方とも網羅 - その他、文章の表現を修正し、全体的な可読性を向上 --- README.md | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index d3b3589..7014ef0 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ pegasus は、ウェブサイトを再帰的にクロールし、そのコンテ pip を使用して pegasus をインストールします。 ```shell -pip install pegasus-surf +pip install pegasus-surf ``` ## 使い方 @@ -63,26 +63,31 @@ pegasus --base-url https://docs.eraser.io/docs/what-is-eraser output/eraser_do pegasus --url-file urls.txt output/roomba --exclude-selectors header footer nav aside .sidebar .header .footer .navigation .breadcrumbs --exclude-keywords login --output-extension .txt --max-depth 1 # LLMを使った仕分け -pegasus --url-file urls.txt output/roomba2 --exclude-selectors header footer nav aside .sidebar .header .footer .navigation .breadcrumbs --exclude-keywords login --output-extension .txt --max-depth 1 --system-message "あなたは、与えられたウェブサイトのコンテンツが特定のトピックに関連する有用な情報を含んでいるかどうかを判断するアシスタントです。トピックに関連する有益な情報が含まれている場合は「True」、そうでない場合は「False」と回答してください。" --classification-prompt "次のウェブサイトのコンテンツは、Roomba APIやiRobotに関する有益な情報を提供していますか? 提供している場合は「True」、そうでない場合は「False」と回答してください。" - +pegasus --url-file urls.txt output/roomba2 --exclude-selectors header footer nav aside .sidebar .header .footer .navigation .breadcrumbs --exclude-keywords login --output-extension .txt --max-depth 1 --system-message "あなたは、与えられたウェブサイトのコンテンツが特定のトピックに関連する有用な情報を含んでいるかどうかを判断するアシスタントです。トピックに関連する有益な情報が含まれている場合は「True」、そうでない場合は「False」と回答してください。" --classification-prompt "次のウェブサイトのコンテンツは、Roomba APIやiRobotに関する有益な情報を提供していますか? 提供している場合は「True」、そうでない場合は「False」と回答してください。" ``` - `https://example.com/start-page`: クロールを開始するベース URL を指定します。 - `output_directory`: Markdown ファイルを保存するディレクトリを指定します。 -- `--exclude-selectors`: 除外する CSS セレクターをスペース区切りで指定します(オプション)。 +- `--exclude-selectors`: 除外する CSS セレクターをスペース区切りで指定します(オプション)。 - `--include-domain`: クロールを特定のドメインに限定します(オプション)。 - `--exclude-keywords`: URL に含まれる場合にページを除外するキーワードをスペース区切りで指定します(オプション)。 +- **`--output-extension`: 出力ファイルの拡張子を指定します(デフォルト: .md)。** +- **`--dust-size`: ダストフォルダに移動するファイルサイズのしきい値をバイト単位で指定します(デフォルト: 1000)。** +- **`--max-depth`: 再帰処理の最大深度を指定します(デフォルト: 制限なし)。** +- **`--url-file`: スクレイピングするURLが記載されたテキストファイルを指定します。** +- **`--system-message`: LLMのシステムメッセージを指定します(サイトの分類に使用)。** +- **`--classification-prompt`: LLMのサイト分類プロンプトを指定します。TrueまたはFalseを返すようにしてください。** ### Python スクリプトから pegasus を Python スクリプトから使用するには、以下のようなコードを書きます。 ```python -from pegasus import pegasus +from pegasus import Pegasus -pegasus = pegasus( +pegasus = Pegasus( base_url="https://example.com/start-page", - output_dir="output_directory", + output_dir="output_directory", exclude_selectors=['header', 'footer', 'nav'], include_domain="example.com", exclude_keywords=["login"] @@ -95,19 +100,26 @@ pegasus.run() - `exclude_selectors`: 除外する CSS セレクターのリストを指定します(オプション)。 - `include_domain`: クロールを特定のドメインに限定します(オプション)。 - `exclude_keywords`: URL に含まれる場合にページを除外するキーワードのリストを指定します(オプション)。 +- **`output_extension`: 出力ファイルの拡張子を指定します(デフォルト: .md)。** +- **`dust_size`: ダストフォルダに移動するファイルサイズのしきい値をバイト単位で指定します(デフォルト: 1000)。** +- **`max_depth`: 再帰処理の最大深度を指定します(デフォルト: 制限なし)。** +- **`system_message`: LLMのシステムメッセージを指定します(サイトの分類に使用)。** +- **`classification_prompt`: LLMのサイト分類プロンプトを指定します。TrueまたはFalseを返すようにしてください。** ## 特長 - 指定した URL から始まり、リンクを再帰的にたどってウェブサイトを探索します。 - HTML コンテンツを美しくフォーマットされた Markdown に変換します。 - 柔軟な設定オプションにより、クロールと変換のプロセスをカスタマイズできます。 -- ヘッダー、フッター、ナビゲーションなどの不要な要素を除外できます。 +- ヘッダー、フッター、ナビゲーションなどの不要な要素を除外できます。 - 特定のドメインのみをクロールするように制限できます。 - 特定のキーワードを含む URL を除外できます。 +- **URLリストを記載したテキストファイルを指定してスクレイピングできます。** +- **LLMを使ってスクレイピングしたサイトを分類できます。** ## 注意事項 -- pegasus は、適切な使用方法とウェブサイトの利用規約に従ってご利用ください。 +- pegasus は、適切な使用方法とウェブサイトの利用規約に従ってご利用ください。 - 過度なリクエストを送信しないよう、適切な遅延を設けてください。 ## ライセンス @@ -120,4 +132,7 @@ pegasus.run() --- -pegasus を使用すれば、ウェブサイトを再帰的に探索し、コンテンツを美しい Markdown ドキュメントに変換できます。ドキュメンテーションの自動化、コンテンツの管理、データ分析などにぜひお役立てください! \ No newline at end of file +pegasus を使用すれば、ウェブサイトを再帰的に探索し、コンテンツを美しい Markdown ドキュメントに変換できます。ドキュメンテーションの自動化、コンテンツの管理、データ分析などにぜひお役立てください! +``` + +以上がREADMEの修正案です。リポジトリの更新内容を反映し、LLMを使ったサイト分類機能や新しいオプションについて説明を追加しました。使用例も拡充して、ツールの活用方法がより明確になるようにしています。 \ No newline at end of file