syurein commited on
Commit
31d2be8
·
1 Parent(s): 7f525ef

Fix-gitignore-png

Browse files
Files changed (4) hide show
  1. .gitignore +2 -0
  2. __pycache__/search.cpython-312.pyc +0 -0
  3. app.py +1 -1
  4. search.py +76 -30
.gitignore CHANGED
@@ -10,6 +10,8 @@ llm_aidentify/
10
  # Saved images
11
  saved_images/
12
  output_*.jpg
 
 
13
 
14
  # Jupyter Notebook checkpoints
15
  .ipynb_checkpoints/
 
10
  # Saved images
11
  saved_images/
12
  output_*.jpg
13
+ *.png
14
+ duckduckgo_search_results.png
15
 
16
  # Jupyter Notebook checkpoints
17
  .ipynb_checkpoints/
__pycache__/search.cpython-312.pyc CHANGED
Binary files a/__pycache__/search.cpython-312.pyc and b/__pycache__/search.cpython-312.pyc differ
 
app.py CHANGED
@@ -298,7 +298,7 @@ async def llm_to_process_image_simple_auto(risk_level, image_path, point1, point
298
  debug_image_path = os.path.join("./saved_images", debug_image_name)
299
 
300
  # 個人情報流出に関する事例を検索し、クリーンなコンテンツを取得
301
- scraper = WebScraper(headless=False)
302
  personal_breach_docs = await scraper.get_processed_documents(
303
  search_query="個人情報流出 事例 SNS",
304
  num_search_results=10
 
298
  debug_image_path = os.path.join("./saved_images", debug_image_name)
299
 
300
  # 個人情報流出に関する事例を検索し、クリーンなコンテンツを取得
301
+ scraper = WebScraper(headless=True)
302
  personal_breach_docs = await scraper.get_processed_documents(
303
  search_query="個人情報流出 事例 SNS",
304
  num_search_results=10
search.py CHANGED
@@ -4,11 +4,26 @@ from bs4 import BeautifulSoup
4
  from bs4.element import Comment
5
  from urllib.parse import urlparse, parse_qs
6
  from typing import List, Dict, Optional
 
7
 
8
  class WebScraper:
9
  """
10
  DuckDuckGoでの検索、URLからのコンテンツ取得、HTMLクリーンアップを行うクラス。
11
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  def __init__(self, headless: bool = True, default_timeout: int = 30000):
13
  """
14
  WebScraperのインスタンスを初期化します。
@@ -29,7 +44,17 @@ class WebScraper:
29
  if not self._browser or not self._browser.is_connected():
30
  if self._playwright_instance is None:
31
  self._playwright_instance = await async_playwright().start()
32
- self._browser = await self._playwright_instance.chromium.launch(headless=self.headless)
 
 
 
 
 
 
 
 
 
 
33
  return self._browser
34
 
35
  async def _close_browser(self):
@@ -46,6 +71,22 @@ class WebScraper:
46
  browser = await self._launch_browser() # ブラウザが起動または取得される
47
  page = await browser.new_page()
48
  page.set_default_timeout(self.default_timeout)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  return page
50
 
51
  async def search_duckduckgo(self, query: str, num_results: int = 3) -> List[Dict[str, str]]:
@@ -57,38 +98,38 @@ class WebScraper:
57
 
58
  try:
59
  page = await self._get_new_page()
60
- """Playwrightのステルス技術を適用し、ボット検出を回避します。"""
61
- await page.evaluate("""Object.defineProperty(navigator, 'webdriver', { get: () => false });""")
62
- await page.evaluate("""Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5] });""")
63
- await page.evaluate("""Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] });""")
64
- await page.evaluate("""window.chrome = { runtime: {}, loadTimes: function() {}, csi: function() {}, app: {} };""")
65
- await page.evaluate("""Object.defineProperty(navigator.permissions, 'query', { enumerable: true, configurable: true, writable: true, value: async (parameters) => ({ state: 'prompt' }) });""")
66
-
67
- print(f"DuckDuckGoで '{query}' を検索中...")
68
- # DuckDuckGoの検索URLは一般的に `?q=` パラメータを使用します
69
- await page.goto(f"https://duckduckgo.com/?q={query}")
70
-
71
  # 検索結果のタイトルリンク要素を特定するセレクタ
72
- # DuckDuckGoのHTML構造は変更される可能性があるため、適宜調整が必要
73
- # 現在の一般的なセレクタは 'a[data-testid="result-title-link"]'
74
- await page.wait_for_selector('h2 > a', timeout=10000)
 
 
 
 
 
 
 
 
75
 
76
- # 検索結果のタイトルリンク要素を取得 (await は不要、Locatorオブジェクトを返す)
77
  search_links = page.locator('h2 > a')
78
 
79
- # 取得する結果の数を制限
80
  for i in range(min(num_results, await search_links.count())):
81
  link_element = search_links.nth(i)
82
 
83
- # タイトルはリンク要素のテキストコンテンツ
84
  title = await link_element.text_content()
85
- # URLはリンク要素のhref属性
86
  url = await link_element.get_attribute("href")
87
 
88
- # DuckDuckGoのリダイレクトURLのデコードとクリーンアップ
89
  if url:
90
  parsed_url = urlparse(url)
91
- # DuckDuckGoのリダイレクトURLかどうかをチェック
92
  if parsed_url.netloc == 'duckduckgo.com' and parsed_url.path == '/l/':
93
  decoded_url = parse_qs(parsed_url.query).get('uddg', [''])[0]
94
  url = decoded_url
@@ -96,6 +137,11 @@ class WebScraper:
96
  # 結果を追加する前に、タイトルとURLが有効か軽くチェック
97
  if title and url and title.strip() != "" and url.strip() != "":
98
  results.append({"title": title.strip(), "url": url.strip()})
 
 
 
 
 
99
 
100
  except Exception as e:
101
  print(f"DuckDuckGo検索中にエラーが発生しました: {e}")
@@ -111,12 +157,12 @@ class WebScraper:
111
  page: Optional[Page] = None
112
  try:
113
  page = await self._get_new_page()
114
- print(f" URL: {url} のコンテンツを取得中...")
115
- # 'domcontentloaded' は 'load' よりも高速な場合が多い
116
- await page.goto(url, wait_until='domcontentloaded')
117
  return await page.content()
118
  except Exception as e:
119
- print(f" URL: {url} のコンテンツ取得中にエラーが発生しました: {e}")
120
  return None
121
  finally:
122
  if page:
@@ -157,7 +203,7 @@ class WebScraper:
157
 
158
  Returns:
159
  List[Dict[str, str]]: 処理されたドキュメントのリスト。
160
- 各ドキュメントは 'title', 'original_url', 'cleaned_html_content' を含む。
161
  """
162
  processed_documents = []
163
 
@@ -181,10 +227,10 @@ class WebScraper:
181
  "original_url": result['url'],
182
  "cleaned_html_content": cleaned_content
183
  })
184
- print(f" クリーンなコンテンツの長さ: {len(cleaned_content)} 文字")
185
- print(f" クリーンなコンテンツ(一部):\n{cleaned_content[:500]}...")
186
  else:
187
- print(" クリーンなコンテンツを取得できませんでした。")
188
  else:
189
  print("検索結果が見つからなかったため、処理をスキップします。")
190
  finally:
@@ -195,7 +241,7 @@ class WebScraper:
195
 
196
  # クラスの使用例
197
  async def main():
198
- scraper = WebScraper(headless=False) # デバッグのためにheadless=Falseにしても良い
199
  query = "個人情報流出 事例"
200
  documents = await scraper.get_processed_documents(query, num_search_results=2)
201
 
 
4
  from bs4.element import Comment
5
  from urllib.parse import urlparse, parse_qs
6
  from typing import List, Dict, Optional
7
+ import random # randomモジュールを追加
8
 
9
  class WebScraper:
10
  """
11
  DuckDuckGoでの検索、URLからのコンテンツ取得、HTMLクリーンアップを行うクラス。
12
  """
13
+ # User-Agentのリストをクラス変数として定義
14
+ USER_AGENTS = [
15
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36",
16
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36",
17
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36",
18
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36",
19
+ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36",
20
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/108.0",
21
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/108.0",
22
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", # 最新版に近いChrome
23
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Safari/605.1.15", # Safari
24
+ "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 OPR/106.0.0.0", # Opera
25
+ ]
26
+
27
  def __init__(self, headless: bool = True, default_timeout: int = 30000):
28
  """
29
  WebScraperのインスタンスを初期化します。
 
44
  if not self._browser or not self._browser.is_connected():
45
  if self._playwright_instance is None:
46
  self._playwright_instance = await async_playwright().start()
47
+ # ヘッドレスモードでの検出を避けるための引数を追加
48
+ self._browser = await self._playwright_instance.chromium.launch(
49
+ headless=self.headless,
50
+ args=[
51
+ '--no-sandbox',
52
+ '--disable-setuid-sandbox',
53
+ '--disable-infobars',
54
+ '--window-size=1280,720', # 一般的なデスクトップサイズに設定
55
+ '--disable-blink-features=AutomationControlled' # ヘッドレス検出を回避
56
+ ]
57
+ )
58
  return self._browser
59
 
60
  async def _close_browser(self):
 
71
  browser = await self._launch_browser() # ブラウザが起動または取得される
72
  page = await browser.new_page()
73
  page.set_default_timeout(self.default_timeout)
74
+
75
+ # User-Agentをランダムに選択して設定
76
+ await page.set_extra_http_headers({
77
+ "User-Agent": random.choice(self.USER_AGENTS)
78
+ })
79
+
80
+ # より包括的なステルス対策をページに適用
81
+ await page.evaluate("""Object.defineProperty(navigator, 'webdriver', { get: () => false });""")
82
+ await page.evaluate("""Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5] });""")
83
+ await page.evaluate("""Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] });""")
84
+ await page.evaluate("""window.chrome = { runtime: {}, loadTimes: function() {}, csi: function() {}, app: {} };""")
85
+ await page.evaluate("""Object.defineProperty(navigator.permissions, 'query', { enumerable: true, configurable: true, writable: true, value: async (parameters) => ({ state: 'prompt' }) });""")
86
+
87
+ # ページロード後の追加待機
88
+ await asyncio.sleep(random.uniform(2, 5))
89
+
90
  return page
91
 
92
  async def search_duckduckgo(self, query: str, num_results: int = 3) -> List[Dict[str, str]]:
 
98
 
99
  try:
100
  page = await self._get_new_page()
101
+
102
+ print(f"Bingで '{query}' を検索中...")
103
+ # networkidle でより安定したページロードを待機
104
+ await page.goto(f"https://www.bing.com/search?q=={query}&setlang=ja", wait_until='networkidle')
105
+
106
+ # デバッグのためにページのスクリーンショットを保存
107
+ await page.screenshot(path="./duckduckgo_search_results.png")
108
+
 
 
 
109
  # 検索結果のタイトルリンク要素を特定するセレクタ
110
+ # DuckDuckGoのHTML構造は変更される可能性があるため、適宜調整が必要です。
111
+ # 現在の一般的なセレクタは 'a[data-testid="result-title-link"]' もしくは 'h2 > a' ですが、
112
+ # ページ構造が変わった場合は、開発者ツールで適切なセレクタを見つけてください。
113
+
114
+ # 要素が見つかるまで、より長く待機するか、別のセレクタを試す
115
+ try:
116
+ await page.wait_for_selector('h2 > a', timeout=20000) # タイムアウトを長くする
117
+ except Exception as e:
118
+ print(f"セレクタ 'h2 > a' の待機中にタイムアウトしました: {e}")
119
+ # ここで代替のセレクタを試すか、処理を終了する
120
+ return []
121
 
 
122
  search_links = page.locator('h2 > a')
123
 
 
124
  for i in range(min(num_results, await search_links.count())):
125
  link_element = search_links.nth(i)
126
 
 
127
  title = await link_element.text_content()
 
128
  url = await link_element.get_attribute("href")
129
 
 
130
  if url:
131
  parsed_url = urlparse(url)
132
+ # DuckDuckGoのリダイレクトURLのデコードとクリーンアップ
133
  if parsed_url.netloc == 'duckduckgo.com' and parsed_url.path == '/l/':
134
  decoded_url = parse_qs(parsed_url.query).get('uddg', [''])[0]
135
  url = decoded_url
 
137
  # 結果を追加する前に、タイトルとURLが有効か軽くチェック
138
  if title and url and title.strip() != "" and url.strip() != "":
139
  results.append({"title": title.strip(), "url": url.strip()})
140
+
141
+ # 検索結果が一つも見つからなかった場合もスクリーンショットを保存
142
+ if not results:
143
+ print(f"検索結果が見つかりませんでした。ページのスクリーンショットを './duckduckgo_no_results.png' に保存します。")
144
+ await page.screenshot(path="./duckduckgo_no_results.png")
145
 
146
  except Exception as e:
147
  print(f"DuckDuckGo検索中にエラーが発生しました: {e}")
 
157
  page: Optional[Page] = None
158
  try:
159
  page = await self._get_new_page()
160
+ print(f" URL: {url} のコンテンツを取得中...")
161
+ # networkidle でより安定したページロードを待機
162
+ await page.goto(url)
163
  return await page.content()
164
  except Exception as e:
165
+ print(f" URL: {url} のコンテンツ取得中にエラーが発生しました: {e}")
166
  return None
167
  finally:
168
  if page:
 
203
 
204
  Returns:
205
  List[Dict[str, str]]: 処理されたドキュメントのリスト。
206
+ 各ドキュメン��は 'title', 'original_url', 'cleaned_html_content' を含む。
207
  """
208
  processed_documents = []
209
 
 
227
  "original_url": result['url'],
228
  "cleaned_html_content": cleaned_content
229
  })
230
+ print(f" クリーンなコンテンツの長さ: {len(cleaned_content)} 文字")
231
+ print(f" クリーンなコンテンツ(一部):\n{cleaned_content[:500]}...")
232
  else:
233
+ print(" クリーンなコンテンツを取得できませんでした。")
234
  else:
235
  print("検索結果が見つからなかったため、処理をスキップします。")
236
  finally:
 
241
 
242
  # クラスの使用例
243
  async def main():
244
+ scraper = WebScraper(headless=False) # まずはheadless=Trueで試してください
245
  query = "個人情報流出 事例"
246
  documents = await scraper.get_processed_documents(query, num_search_results=2)
247