-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscraper.py
More file actions
200 lines (154 loc) · 6.19 KB
/
Copy pathscraper.py
File metadata and controls
200 lines (154 loc) · 6.19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
from playwright.sync_api import sync_playwright
from bs4 import BeautifulSoup
import socket
import json
from datetime import date
import time
import random
import os
import requests
HOST = "127.0.0.1"
PORT = 8080
def send_discord_alert(name, new_price, old_price, url):
webhook_file = "webhook.json"
if not os.path.exists(webhook_file):
print("[Warning] No webhook found")
return
with open(webhook_file, "r", encoding = "utf-8") as f:
secrets = json.load(f)
webhook_url = secrets.get("discord_webhook")
if not webhook_url:
return
sale = (old_price - new_price) * 100 // old_price
message = {
"content": None,
"embeds": [
{
"title": "Found Sale!",
"description": f"{name} is on sale",
"color": 65280,
"fields": [
{"name": "Old price", "value": f"{old_price}", "inline": True},
{"name": "New price", "value": f"{new_price}", "inline": True},
{"name": "Difference", "value": f"{sale}%", "inline": True}
],
"url": url
}
]
}
try:
response = requests.post(webhook_url, json = message)
if response.status_code == 204:
print("[Webhook] Message sent")
else:
print(f"[Webhook] Error. Status code: {response.status_code}")
except Exception as e:
print(f"[Webhook] Connection error: {e}")
def parse_html(html_content):
# Transform HTML element into clean price
# Initialize BeautifulSoup to read HTML
soup = BeautifulSoup(html_content, "html.parser")
# Search for <span> tag with class "Price-int"
prices = soup.find_all("span", class_ = "Price-int")
if len(prices) < 2:
print("\nError: Page structure has changed, failed to find enough price elements")
# Index 1 is chosen, based on site architecture
price_element = prices[1]
price_text = price_element.text.strip()
try:
clean_price = int(price_text.replace('.',''))
return clean_price
except ValueError:
print(f"\nError: could not convert price: {price_text}")
return None
def save_price_to_db(product_id, product_name, current_price, product_url):
# Connect to OffsetDB and store data
today_str = date.today().isoformat()
try:
# Open TCP socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
# Check if product exists
get_command = f"GET {product_id}\n"
s.sendall(get_command.encode("utf-8"))
response = s.recv(4096).decode('utf-8').strip()
product_data = {
"name": product_name,
"url": product_url,
"history": []
}
if response != "Key not found" and not response.startswith("Error"):
try:
product_data = json.loads(response)
except json.JSONDecodeError:
print("[DB Client] Log contains corrupted data, resetting history")
# Add today's price to history
clean_history = [entry for entry in product_data["history"] if entry["date"] != today_str]
# Look for price changes
if len(clean_history) > 0:
last_price = clean_history[-1]["price"]
if current_price < last_price:
print(f"[Sale detected] Price: {last_price} -> {current_price}")
send_discord_alert(product_name, current_price, last_price, product_url)
clean_history.append({
"date": today_str,
"price": current_price
})
product_data["history"] = clean_history
# Convert to JSON and send
json_payload = json.dumps(product_data)
add_command = f"ADD {product_id} {json_payload}\n"
s.sendall(add_command.encode('utf-8'))
# Wait for confirmation from server
confirm = s.recv(1024).decode("utf-8").strip()
if confirm == "OK":
print(f"[DB CLIENT] History for {product_id} updated")
else:
print(f"[DB Client] Server returned error: {confirm}")
s.close()
except Exception as e:
print(f"[DB Client] Connection error: {e}")
if __name__ == "__main__":
product_file = "products.json"
if not os.path.exists(product_file):
print(f"[Error] Product file not found")
exit(1)
try:
with open(product_file, "r", encoding = "utf-8") as f:
products = json.load(f)
except json.JSONDecodeError:
print(f"[Error] Invalid product file")
exit(1)
print(f"--- Processing {len(products)} products ---")
# Open the browser only once for all products
with sync_playwright() as p:
browser = p.firefox.launch(headless = True)
for product in products:
# Create new tab for each product
context = browser.new_context(
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:120.0) Gecko/20100101 Firefox/120.0",
viewport={'width': 1920, 'height': 1080}
)
page = context.new_page()
print(f"\n[Scraper] Accessing {product["name"]}...")
try:
page.goto(product["url"], timeout = 60000)
page.wait_for_load_state("domcontentloaded")
html = page.content()
price = parse_html(html)
if price:
print(f"[Parser] Price extracted: {price} RON")
save_price_to_db(product["id"], product["name"], price, product["url"])
print(f"[DB Client] Saved to OffsetDB")
else:
print("[Error] Could not extract price")
except Exception as err:
print(f"[Error] Could not access page: {err}")
finally:
context.close()
# Randomized pause to prevent triggering bot detection
wait_time = random.uniform(3, 6)
print(f"[Scraper] Waiting {wait_time:.2f} seconds before moving to next product")
time.sleep(wait_time)
print("\n--- Processing complete ---")
browser.close()