Compare commits
4 Commits
0710af0f82
...
7646f6f68f
| Author | SHA1 | Date | |
|---|---|---|---|
| 7646f6f68f | |||
| e0cb02c490 | |||
| 6b8728360c | |||
| d57f7d75ce |
220
Seedbox/60 AktualizaceSeeders.py
Normal file
220
Seedbox/60 AktualizaceSeeders.py
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
import pymysql
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# CONFIG
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
COOKIE_FILE = "sktorrent_cookies.json"
|
||||||
|
|
||||||
|
BASE_URL = "https://sktorrent.eu/torrent/torrents.php?active=0&category=24&order=data&by=DESC"
|
||||||
|
|
||||||
|
SLEEP_BETWEEN_PAGES = 2.0 # sekundy mezi stránkami (web nás neblokuje)
|
||||||
|
MAX_PAGES = 300 # pojistka — skript se zastaví nejpozději zde
|
||||||
|
|
||||||
|
# Kolik stránek za sebou bez jediné shody v DB = konec (dorazili jsme k novým torrentům)
|
||||||
|
STOP_AFTER_EMPTY_PAGES = 5
|
||||||
|
# Kolik 403 chyb za sebou = přerušit (web nás blokuje)
|
||||||
|
STOP_AFTER_403 = 3
|
||||||
|
|
||||||
|
DB_CONFIG = {
|
||||||
|
"host": "192.168.1.76",
|
||||||
|
"port": 3306,
|
||||||
|
"user": "root",
|
||||||
|
"password": "Vlado9674+",
|
||||||
|
"database": "torrents",
|
||||||
|
"charset": "utf8mb4",
|
||||||
|
"autocommit": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# CONNECT
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
def connect_db():
|
||||||
|
return pymysql.connect(**DB_CONFIG)
|
||||||
|
|
||||||
|
|
||||||
|
def build_session():
|
||||||
|
with open(COOKIE_FILE, "r", encoding="utf-8") as f:
|
||||||
|
cookies = json.load(f)
|
||||||
|
session = requests.Session()
|
||||||
|
session.headers["User-Agent"] = (
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
|
||||||
|
)
|
||||||
|
for c in cookies:
|
||||||
|
session.cookies.set(c["name"], c["value"], domain=c.get("domain", ""))
|
||||||
|
return session
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# PARSE ONE PAGE
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
def parse_page(html):
|
||||||
|
"""
|
||||||
|
Vrátí seznam dict: {hash, seeders, leechers}
|
||||||
|
"""
|
||||||
|
soup = BeautifulSoup(html, "html.parser")
|
||||||
|
results = []
|
||||||
|
|
||||||
|
for row in soup.select("table tr"):
|
||||||
|
cells = row.find_all("td")
|
||||||
|
if len(cells) != 7:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# td[1] musí mít odkaz download.php?id=<hash>
|
||||||
|
dl_link = cells[1].find("a", href=re.compile(r"download\.php\?id="))
|
||||||
|
if not dl_link:
|
||||||
|
continue
|
||||||
|
|
||||||
|
match = re.search(r"id=([a-f0-9]+)", dl_link["href"])
|
||||||
|
if not match:
|
||||||
|
continue
|
||||||
|
|
||||||
|
torrent_hash = match.group(1).lower()
|
||||||
|
|
||||||
|
# seeders = td[4], leechers = td[5]
|
||||||
|
seeders_text = cells[4].get_text(strip=True)
|
||||||
|
leechers_text = cells[5].get_text(strip=True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
seeders = int(seeders_text)
|
||||||
|
except ValueError:
|
||||||
|
seeders = 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
leechers = int(leechers_text)
|
||||||
|
except ValueError:
|
||||||
|
leechers = 0
|
||||||
|
|
||||||
|
results.append({
|
||||||
|
"hash": torrent_hash,
|
||||||
|
"seeders": seeders,
|
||||||
|
"leechers": leechers,
|
||||||
|
})
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# MAIN
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
def main():
|
||||||
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
|
|
||||||
|
print("=" * 60)
|
||||||
|
print("AKTUALIZACE SEEDERS / LEECHERS — sktorrent.eu")
|
||||||
|
print(f"Spuštěno: {datetime.now():%Y-%m-%d %H:%M:%S}")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
session = build_session()
|
||||||
|
db = connect_db()
|
||||||
|
cursor = db.cursor()
|
||||||
|
|
||||||
|
# Zjisti max stránku
|
||||||
|
r0 = session.get(f"{BASE_URL}&page=0", timeout=15)
|
||||||
|
all_page_nums = [int(m.group(1)) for m in re.finditer(r"page=(\d+)", r0.text)]
|
||||||
|
max_page = max(all_page_nums) if all_page_nums else MAX_PAGES
|
||||||
|
print(f"Max stránka na webu: {max_page}")
|
||||||
|
print(f"Prochází od stránky {max_page} směrem dolů...\n")
|
||||||
|
|
||||||
|
total_pages = 0
|
||||||
|
total_parsed = 0
|
||||||
|
total_updated = 0
|
||||||
|
total_skipped = 0
|
||||||
|
consecutive_empty = 0 # stránky za sebou bez jediné shody v DB
|
||||||
|
consecutive_403 = 0 # 403 chyby za sebou
|
||||||
|
|
||||||
|
# Procházíme od nejstarší stránky (konec) k nejnovější (začátek)
|
||||||
|
for page in range(max_page, -1, -1):
|
||||||
|
|
||||||
|
url = f"{BASE_URL}&page={page}"
|
||||||
|
try:
|
||||||
|
r = session.get(url, timeout=15)
|
||||||
|
r.raise_for_status()
|
||||||
|
consecutive_403 = 0 # reset po úspěchu
|
||||||
|
except requests.exceptions.HTTPError as e:
|
||||||
|
if e.response is not None and e.response.status_code == 403:
|
||||||
|
consecutive_403 += 1
|
||||||
|
print(f"⚠️ Stránka {page} — 403 Forbidden ({consecutive_403}/{STOP_AFTER_403})")
|
||||||
|
if consecutive_403 >= STOP_AFTER_403:
|
||||||
|
print(f"\n🛑 {STOP_AFTER_403}× 403 za sebou — web nás blokuje, přerušuji.")
|
||||||
|
break
|
||||||
|
time.sleep(5) # pauza po 403
|
||||||
|
else:
|
||||||
|
print(f"⚠️ Stránka {page} — chyba: {e}")
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ Stránka {page} — chyba: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if "login.php" in r.url or "Prihlas sa" in r.text:
|
||||||
|
print("❌ Cookies expiraly — je potřeba se znovu přihlásit (spusť Selenium skript)")
|
||||||
|
break
|
||||||
|
|
||||||
|
rows = parse_page(r.text)
|
||||||
|
|
||||||
|
if not rows:
|
||||||
|
print(f" Stránka {page:3d} → prázdná, konec paginace.")
|
||||||
|
break
|
||||||
|
|
||||||
|
total_pages += 1
|
||||||
|
total_parsed += len(rows)
|
||||||
|
page_updated = 0
|
||||||
|
|
||||||
|
for item in rows:
|
||||||
|
cursor.execute("""
|
||||||
|
UPDATE torrents
|
||||||
|
SET
|
||||||
|
seeders = %s,
|
||||||
|
leechers = %s,
|
||||||
|
qb_last_update = NOW()
|
||||||
|
WHERE torrent_hash = %s
|
||||||
|
""", (item["seeders"], item["leechers"], item["hash"]))
|
||||||
|
|
||||||
|
if cursor.rowcount > 0:
|
||||||
|
total_updated += 1
|
||||||
|
page_updated += 1
|
||||||
|
else:
|
||||||
|
total_skipped += 1
|
||||||
|
|
||||||
|
print(f" Stránka {page:3d} → {len(rows):2d} torrentů, "
|
||||||
|
f"updatováno: {page_updated:2d} (celkem: {total_updated})")
|
||||||
|
|
||||||
|
# Zastavit pokud jsme dorazili do oblasti novějších torrentů (mimo DB)
|
||||||
|
if page_updated == 0:
|
||||||
|
consecutive_empty += 1
|
||||||
|
if consecutive_empty >= STOP_AFTER_EMPTY_PAGES:
|
||||||
|
print(f"\n⏹ {STOP_AFTER_EMPTY_PAGES} stránek po sobě bez shody → "
|
||||||
|
f"dorazili jsme k novějším torrentům, které nejsou v DB. Konec.")
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
consecutive_empty = 0
|
||||||
|
|
||||||
|
time.sleep(SLEEP_BETWEEN_PAGES)
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# SUMMARY
|
||||||
|
# ============================================================
|
||||||
|
print()
|
||||||
|
print("=" * 60)
|
||||||
|
print(f"Hotovo: {datetime.now():%Y-%m-%d %H:%M:%S}")
|
||||||
|
print(f"Stránek zpracováno : {total_pages}")
|
||||||
|
print(f"Záznamů parsováno : {total_parsed}")
|
||||||
|
print(f"DB řádků updatováno: {total_updated}")
|
||||||
|
print(f"Nebylo v DB : {total_skipped}")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
278
Seedbox/70 Manager.py
Normal file
278
Seedbox/70 Manager.py
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Download Manager — UltraCC Seedbox
|
||||||
|
Smyčka každých N minut:
|
||||||
|
1. Dokončené torrenty → odeber z qBittorrentu (data zachovej), zapiš do DB
|
||||||
|
2. Spočítej volné sloty
|
||||||
|
3. Doplň nové torrenty dle priority: seeders + velikost
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pymysql
|
||||||
|
import qbittorrentapi
|
||||||
|
import time
|
||||||
|
import sys
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# CONFIG
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
MAX_CONCURRENT = 20 # max torrentů najednou v qBittorrentu
|
||||||
|
LOOP_INTERVAL = 300 # sekund mezi iteracemi (5 minut)
|
||||||
|
|
||||||
|
QBT_URL = "https://vladob.zen.usbx.me/qbittorrent"
|
||||||
|
QBT_USER = "vladob"
|
||||||
|
QBT_PASS = "jCni3U6d#y4bfcm"
|
||||||
|
|
||||||
|
DB_CONFIG = {
|
||||||
|
"host": "192.168.1.76",
|
||||||
|
"port": 3306,
|
||||||
|
"user": "root",
|
||||||
|
"password": "Vlado9674+",
|
||||||
|
"database": "torrents",
|
||||||
|
"charset": "utf8mb4",
|
||||||
|
"autocommit": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# PRIORITY SQL
|
||||||
|
# Pořadí: nejlepší seeders + nejmenší soubory první
|
||||||
|
# ============================================================
|
||||||
|
SELECT_NEXT = """
|
||||||
|
SELECT id, torrent_hash, torrent_content, title_visible, size_pretty, seeders
|
||||||
|
FROM torrents
|
||||||
|
WHERE
|
||||||
|
torrent_content IS NOT NULL
|
||||||
|
AND qb_completed_datetime IS NULL
|
||||||
|
AND (qb_state IS NULL OR qb_state NOT IN (
|
||||||
|
'incomplete', 'invalid', 'dead',
|
||||||
|
'completed', 'completed_removed'
|
||||||
|
))
|
||||||
|
ORDER BY
|
||||||
|
CASE
|
||||||
|
WHEN seeders >= 3 THEN 1
|
||||||
|
WHEN seeders >= 1 THEN 2
|
||||||
|
ELSE 3
|
||||||
|
END ASC,
|
||||||
|
CASE
|
||||||
|
WHEN size_pretty LIKE '%%MB%%' THEN 0
|
||||||
|
ELSE 1
|
||||||
|
END ASC,
|
||||||
|
seeders DESC,
|
||||||
|
added_datetime DESC
|
||||||
|
LIMIT %s
|
||||||
|
"""
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# CONNECT
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
def connect_db():
|
||||||
|
return pymysql.connect(**DB_CONFIG)
|
||||||
|
|
||||||
|
|
||||||
|
def connect_qbt():
|
||||||
|
qbt = qbittorrentapi.Client(
|
||||||
|
host=QBT_URL,
|
||||||
|
username=QBT_USER,
|
||||||
|
password=QBT_PASS,
|
||||||
|
VERIFY_WEBUI_CERTIFICATE=False,
|
||||||
|
)
|
||||||
|
qbt.auth_log_in()
|
||||||
|
return qbt
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# STEP 1: Handle completed torrents
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
def handle_completed(qbt, cursor):
|
||||||
|
"""
|
||||||
|
Dokončené torrenty odebere z qBittorrentu (data zůstanou)
|
||||||
|
a zapíše qb_completed_datetime do DB.
|
||||||
|
"""
|
||||||
|
removed = 0
|
||||||
|
for t in qbt.torrents_info():
|
||||||
|
if not t.completion_on:
|
||||||
|
continue
|
||||||
|
|
||||||
|
thash = t.hash.lower()
|
||||||
|
completed_dt = datetime.fromtimestamp(t.completion_on)
|
||||||
|
|
||||||
|
try:
|
||||||
|
qbt.torrents_delete(torrent_hashes=thash, delete_files=False)
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ⚠️ Nelze odebrat {t.name[:40]}: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
cursor.execute("""
|
||||||
|
UPDATE torrents
|
||||||
|
SET
|
||||||
|
qb_state = 'completed_removed',
|
||||||
|
qb_progress = 100,
|
||||||
|
qb_completed_datetime = COALESCE(qb_completed_datetime, %s),
|
||||||
|
qb_last_update = NOW()
|
||||||
|
WHERE torrent_hash = %s OR qb_hash = %s
|
||||||
|
""", (completed_dt, thash, thash))
|
||||||
|
|
||||||
|
print(f" ✅ Dokončen a odebrán: {t.name[:50]}")
|
||||||
|
removed += 1
|
||||||
|
|
||||||
|
return removed
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# STEP 2: Count active (non-completed) torrents in qBittorrent
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
def get_active_hashes(qbt):
|
||||||
|
"""
|
||||||
|
Vrátí sadu hashů torrentů, které jsou aktuálně v qBittorrentu.
|
||||||
|
"""
|
||||||
|
return {t.hash.lower() for t in qbt.torrents_info()}
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# STEP 3: Add new torrents
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
def add_torrents(qbt, cursor, active_hashes, slots):
|
||||||
|
"""
|
||||||
|
Vybere z DB torrenty dle priority a přidá je do qBittorrentu.
|
||||||
|
Přeskočí ty, které jsou tam již nahrané (dle qb_hash / active_hashes).
|
||||||
|
"""
|
||||||
|
# Načti více než slots, abychom mohli přeskočit ty v qB
|
||||||
|
cursor.execute(SELECT_NEXT, (slots * 3,))
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
|
||||||
|
added = 0
|
||||||
|
for row in rows:
|
||||||
|
if added >= slots:
|
||||||
|
break
|
||||||
|
|
||||||
|
t_id, t_hash, content, title, size, seeders = row
|
||||||
|
t_hash = t_hash.lower() if t_hash else ""
|
||||||
|
|
||||||
|
if t_hash in active_hashes:
|
||||||
|
# Aktualizuj příznak v DB pokud tam už je
|
||||||
|
cursor.execute("""
|
||||||
|
UPDATE torrents SET qb_added=1, qb_last_update=NOW()
|
||||||
|
WHERE id=%s AND (qb_added IS NULL OR qb_added=0)
|
||||||
|
""", (t_id,))
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not content:
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
qbt.torrents_add(
|
||||||
|
torrent_files={f"{t_hash}.torrent": content},
|
||||||
|
is_paused=False,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ❌ Nelze přidat {(title or '')[:40]}: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
cursor.execute("""
|
||||||
|
UPDATE torrents
|
||||||
|
SET qb_added=1, qb_state='added', qb_last_update=NOW()
|
||||||
|
WHERE id=%s
|
||||||
|
""", (t_id,))
|
||||||
|
|
||||||
|
print(f" ➕ Přidán: {(title or '')[:45]} "
|
||||||
|
f"| {size or '?':>10} | seeds={seeders}")
|
||||||
|
added += 1
|
||||||
|
time.sleep(0.3)
|
||||||
|
|
||||||
|
return added
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# MAIN LOOP
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
def main():
|
||||||
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
|
|
||||||
|
print("=" * 60)
|
||||||
|
print("DOWNLOAD MANAGER — UltraCC Seedbox")
|
||||||
|
print(f"MAX_CONCURRENT = {MAX_CONCURRENT} | interval = {LOOP_INTERVAL}s")
|
||||||
|
print("Ctrl+C pro zastavení")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
db = connect_db()
|
||||||
|
cursor = db.cursor()
|
||||||
|
|
||||||
|
iteration = 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
iteration += 1
|
||||||
|
now = datetime.now()
|
||||||
|
print(f"\n{'─'*60}")
|
||||||
|
print(f" Iterace {iteration} — {now:%Y-%m-%d %H:%M:%S}")
|
||||||
|
print(f"{'─'*60}")
|
||||||
|
|
||||||
|
# Připoj se k qBittorrentu (nové spojení každou iteraci)
|
||||||
|
try:
|
||||||
|
qbt = connect_qbt()
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ❌ Nelze se připojit k qBittorrentu: {e}")
|
||||||
|
print(f" ⏳ Zkusím za {LOOP_INTERVAL}s...")
|
||||||
|
time.sleep(LOOP_INTERVAL)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# --- Krok 1: Dokončené ---
|
||||||
|
print(" [1] Kontrola dokončených...")
|
||||||
|
removed = handle_completed(qbt, cursor)
|
||||||
|
if removed == 0:
|
||||||
|
print(" Žádné dokončené.")
|
||||||
|
|
||||||
|
# --- Krok 2: Stav slotů ---
|
||||||
|
active_hashes = get_active_hashes(qbt)
|
||||||
|
active = len(active_hashes)
|
||||||
|
slots = max(0, MAX_CONCURRENT - active)
|
||||||
|
|
||||||
|
# Statistiky z DB
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT
|
||||||
|
SUM(CASE WHEN qb_completed_datetime IS NOT NULL THEN 1 ELSE 0 END),
|
||||||
|
SUM(CASE WHEN qb_state IS NULL AND torrent_content IS NOT NULL THEN 1 ELSE 0 END),
|
||||||
|
SUM(CASE WHEN qb_state IN ('incomplete','dead') THEN 1 ELSE 0 END)
|
||||||
|
FROM torrents
|
||||||
|
""")
|
||||||
|
db_completed, db_waiting, db_dead = cursor.fetchone()
|
||||||
|
|
||||||
|
print(f" [2] Sloty: {active}/{MAX_CONCURRENT} aktivních "
|
||||||
|
f"| volných: {slots}")
|
||||||
|
print(f" DB — staženo: {db_completed or 0} "
|
||||||
|
f"| čeká: {db_waiting or 0} "
|
||||||
|
f"| dead/incomplete: {db_dead or 0}")
|
||||||
|
|
||||||
|
# --- Krok 3: Doplnění ---
|
||||||
|
if slots > 0:
|
||||||
|
print(f" [3] Doplňuji {slots} torrentů...")
|
||||||
|
added = add_torrents(qbt, cursor, active_hashes, slots)
|
||||||
|
if added == 0:
|
||||||
|
print(" Žádné vhodné torrenty k přidání.")
|
||||||
|
else:
|
||||||
|
print(f" Přidáno: {added}")
|
||||||
|
else:
|
||||||
|
print(f" [3] Sloty plné ({active}/{MAX_CONCURRENT}), přeskakuji.")
|
||||||
|
|
||||||
|
print(f"\n ⏳ Další iterace za {LOOP_INTERVAL // 60} min "
|
||||||
|
f"({(now.replace(second=0, microsecond=0)).__class__.__name__} "
|
||||||
|
f"→ přibližně {LOOP_INTERVAL // 60} min)...")
|
||||||
|
time.sleep(LOOP_INTERVAL)
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n\n🛑 Zastaveno uživatelem.")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
print("👋 Bye.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
286
Seedbox/95 IncrementalImport.py
Normal file
286
Seedbox/95 IncrementalImport.py
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Incremental import — sktorrent.eu
|
||||||
|
- Prochází od nejnovějších torrentů
|
||||||
|
- Stahuje a ukládá .torrent soubory pro nové záznamy
|
||||||
|
- Zastaví se, jakmile narazí na torrent, který už v DB máme
|
||||||
|
- Nevyžaduje Selenium — stačí requests + BeautifulSoup + cookies
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pymysql
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
from pathlib import Path
|
||||||
|
from datetime import datetime
|
||||||
|
import urllib.parse as urlparse
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# CONFIG
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
COOKIE_FILE = Path("sktorrent_cookies.json")
|
||||||
|
|
||||||
|
BASE_URL = (
|
||||||
|
"https://sktorrent.eu/torrent/torrents.php"
|
||||||
|
"?active=0&category=24&order=data&by=DESC"
|
||||||
|
)
|
||||||
|
|
||||||
|
SLEEP_BETWEEN_PAGES = 2.0 # pauza mezi stránkami
|
||||||
|
SLEEP_BEFORE_DOWNLOAD = 1.5 # pauza před stažením každého .torrent souboru
|
||||||
|
|
||||||
|
DB_CONFIG = {
|
||||||
|
"host": "192.168.1.76",
|
||||||
|
"port": 3306,
|
||||||
|
"user": "root",
|
||||||
|
"password": "Vlado9674+",
|
||||||
|
"database": "torrents",
|
||||||
|
"charset": "utf8mb4",
|
||||||
|
"autocommit": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# CONNECT
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
def connect_db():
|
||||||
|
return pymysql.connect(**DB_CONFIG)
|
||||||
|
|
||||||
|
|
||||||
|
def build_session():
|
||||||
|
if not COOKIE_FILE.exists():
|
||||||
|
raise FileNotFoundError(f"Cookie soubor nenalezen: {COOKIE_FILE}")
|
||||||
|
with open(COOKIE_FILE, "r", encoding="utf-8") as f:
|
||||||
|
cookies = json.load(f)
|
||||||
|
session = requests.Session()
|
||||||
|
session.headers["User-Agent"] = (
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
|
||||||
|
)
|
||||||
|
for c in cookies:
|
||||||
|
session.cookies.set(c["name"], c["value"], domain=c.get("domain", ""))
|
||||||
|
return session
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# PARSE ONE LISTING PAGE
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
def parse_page(html):
|
||||||
|
"""
|
||||||
|
Vrátí seznam dict pro každý torrent řádek na stránce.
|
||||||
|
Prázdný seznam = konec paginace nebo chyba.
|
||||||
|
"""
|
||||||
|
soup = BeautifulSoup(html, "html.parser")
|
||||||
|
results = []
|
||||||
|
|
||||||
|
for row in soup.select("table tr"):
|
||||||
|
cells = row.find_all("td")
|
||||||
|
if len(cells) != 7:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# td[1] — odkaz na stažení: download.php?id=<hash>&f=<filename>
|
||||||
|
dl_a = cells[1].find("a", href=re.compile(r"download\.php\?id="))
|
||||||
|
if not dl_a:
|
||||||
|
continue
|
||||||
|
|
||||||
|
download_url = dl_a["href"]
|
||||||
|
if not download_url.startswith("http"):
|
||||||
|
download_url = "https://sktorrent.eu/torrent/" + download_url
|
||||||
|
|
||||||
|
m_hash = re.search(r"id=([a-f0-9A-F]+)", download_url)
|
||||||
|
if not m_hash:
|
||||||
|
continue
|
||||||
|
torrent_hash = m_hash.group(1).lower()
|
||||||
|
|
||||||
|
parsed_dl = urlparse.urlparse(download_url)
|
||||||
|
dl_query = urlparse.parse_qs(parsed_dl.query)
|
||||||
|
torrent_filename = dl_query.get("f", ["unknown.torrent"])[0]
|
||||||
|
|
||||||
|
# td[2] — název, details link, velikost, datum
|
||||||
|
title_a = cells[2].find("a", href=re.compile(r"details\.php\?id="))
|
||||||
|
if not title_a:
|
||||||
|
continue
|
||||||
|
|
||||||
|
title_visible = title_a.get_text(strip=True)
|
||||||
|
title_full = title_a.get("title", title_visible)
|
||||||
|
details_link = title_a["href"]
|
||||||
|
if not details_link.startswith("http"):
|
||||||
|
details_link = "https://sktorrent.eu/torrent/" + details_link
|
||||||
|
|
||||||
|
cell2_text = cells[2].get_text(" ", strip=True)
|
||||||
|
|
||||||
|
size_match = re.search(r"Velkost\s+([\d\.,]+\s*[KMG]B)", cell2_text, re.IGNORECASE)
|
||||||
|
added_match = re.search(r"Pridany\s+(\d+/\d+/\d+)\s+(?:o\s+)?(\d+:\d+)", cell2_text, re.IGNORECASE)
|
||||||
|
|
||||||
|
size_pretty = size_match.group(1).strip() if size_match else None
|
||||||
|
added_mysql = None
|
||||||
|
if added_match:
|
||||||
|
try:
|
||||||
|
d, mo, y = added_match.group(1).split("/")
|
||||||
|
t = added_match.group(2) + ":00"
|
||||||
|
added_mysql = f"{y}-{mo}-{d} {t}"
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# td[0] — kategorie
|
||||||
|
category = cells[0].get_text(strip=True)
|
||||||
|
|
||||||
|
# td[4] seeders, td[5] leechers
|
||||||
|
try:
|
||||||
|
seeders = int(cells[4].get_text(strip=True))
|
||||||
|
except ValueError:
|
||||||
|
seeders = 0
|
||||||
|
try:
|
||||||
|
leechers = int(cells[5].get_text(strip=True))
|
||||||
|
except ValueError:
|
||||||
|
leechers = 0
|
||||||
|
|
||||||
|
results.append({
|
||||||
|
"torrent_hash": torrent_hash,
|
||||||
|
"download_url": download_url,
|
||||||
|
"details_link": details_link,
|
||||||
|
"torrent_filename": torrent_filename,
|
||||||
|
"category": category,
|
||||||
|
"title_visible": title_visible,
|
||||||
|
"title_full": title_full,
|
||||||
|
"size_pretty": size_pretty,
|
||||||
|
"added_datetime": added_mysql,
|
||||||
|
"seeders": seeders,
|
||||||
|
"leechers": leechers,
|
||||||
|
})
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# DOWNLOAD .TORRENT FILE
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
def download_torrent(session, url):
|
||||||
|
try:
|
||||||
|
r = session.get(url, timeout=15)
|
||||||
|
r.raise_for_status()
|
||||||
|
if len(r.content) < 20:
|
||||||
|
return None
|
||||||
|
return r.content
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ⚠️ Stažení selhalo: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# DB INSERT
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
INSERT_SQL = """
|
||||||
|
INSERT INTO torrents (
|
||||||
|
torrent_hash, details_link, download_url, category,
|
||||||
|
title_visible, title_full, size_pretty, added_datetime,
|
||||||
|
seeders, leechers, torrent_filename, torrent_content
|
||||||
|
) VALUES (
|
||||||
|
%(torrent_hash)s, %(details_link)s, %(download_url)s, %(category)s,
|
||||||
|
%(title_visible)s, %(title_full)s, %(size_pretty)s, %(added_datetime)s,
|
||||||
|
%(seeders)s, %(leechers)s, %(torrent_filename)s, %(torrent_content)s
|
||||||
|
)
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
seeders = VALUES(seeders),
|
||||||
|
leechers = VALUES(leechers),
|
||||||
|
download_url = VALUES(download_url),
|
||||||
|
torrent_content = COALESCE(VALUES(torrent_content), torrent_content)
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# MAIN
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
def main():
|
||||||
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
|
|
||||||
|
print("=" * 60)
|
||||||
|
print("INCREMENTAL IMPORT — sktorrent.eu")
|
||||||
|
print(f"Spuštěno: {datetime.now():%Y-%m-%d %H:%M:%S}")
|
||||||
|
print("Pořadí: nejnovější → nejstarší | stop při první shodě")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
session = build_session()
|
||||||
|
db = connect_db()
|
||||||
|
cursor = db.cursor()
|
||||||
|
|
||||||
|
new_count = 0
|
||||||
|
page = 0
|
||||||
|
stop = False
|
||||||
|
|
||||||
|
while not stop:
|
||||||
|
|
||||||
|
url = f"{BASE_URL}&page={page}"
|
||||||
|
try:
|
||||||
|
r = session.get(url, timeout=15)
|
||||||
|
r.raise_for_status()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ Stránka {page} — chyba: {e}")
|
||||||
|
break
|
||||||
|
|
||||||
|
if "login.php" in r.url or "Prihlas sa" in r.text:
|
||||||
|
print("❌ Cookies expiraly — spusť přihlašovací Selenium skript a obnov cookies.")
|
||||||
|
break
|
||||||
|
|
||||||
|
rows = parse_page(r.text)
|
||||||
|
|
||||||
|
if not rows:
|
||||||
|
print(f" Stránka {page} — žádné záznamy, konec.")
|
||||||
|
break
|
||||||
|
|
||||||
|
print(f"\n📄 Stránka {page} ({len(rows)} torrentů)")
|
||||||
|
|
||||||
|
for item in rows:
|
||||||
|
|
||||||
|
# Zkontroluj DB
|
||||||
|
cursor.execute(
|
||||||
|
"SELECT 1 FROM torrents WHERE torrent_hash = %s",
|
||||||
|
(item["torrent_hash"],)
|
||||||
|
)
|
||||||
|
exists = cursor.fetchone()
|
||||||
|
|
||||||
|
if exists:
|
||||||
|
print(f" ⏹ Již v DB: {item['title_visible']} → zastavuji import.")
|
||||||
|
stop = True
|
||||||
|
break
|
||||||
|
|
||||||
|
# Nový torrent — stáhni .torrent soubor
|
||||||
|
print(f" ⬇️ Nový: {item['title_visible']}")
|
||||||
|
time.sleep(SLEEP_BEFORE_DOWNLOAD)
|
||||||
|
|
||||||
|
content = download_torrent(session, item["download_url"])
|
||||||
|
if content:
|
||||||
|
print(f" ✔ Staženo ({len(content):,} B)")
|
||||||
|
else:
|
||||||
|
print(f" ✖ Nepodařilo se stáhnout, ukládám bez obsahu")
|
||||||
|
|
||||||
|
item["torrent_content"] = content
|
||||||
|
cursor.execute(INSERT_SQL, item)
|
||||||
|
new_count += 1
|
||||||
|
|
||||||
|
if not stop:
|
||||||
|
page += 1
|
||||||
|
time.sleep(SLEEP_BETWEEN_PAGES)
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# SUMMARY
|
||||||
|
# ============================================================
|
||||||
|
print()
|
||||||
|
print("=" * 60)
|
||||||
|
print(f"Hotovo: {datetime.now():%Y-%m-%d %H:%M:%S}")
|
||||||
|
print(f"Nových torrentů uloženo : {new_count}")
|
||||||
|
print(f"Stránek prošlo : {page}")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
22
Seedbox/sktorrent_cookies.json
Normal file
22
Seedbox/sktorrent_cookies.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "uid",
|
||||||
|
"value": "646071",
|
||||||
|
"domain": "sktorrent.eu",
|
||||||
|
"path": "/",
|
||||||
|
"expires": 1798003565.462807,
|
||||||
|
"httpOnly": false,
|
||||||
|
"secure": false,
|
||||||
|
"sameSite": "Lax"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pass",
|
||||||
|
"value": "91df6b497860582e09a7b333569d0187",
|
||||||
|
"domain": "sktorrent.eu",
|
||||||
|
"path": "/",
|
||||||
|
"expires": 1798003565.463191,
|
||||||
|
"httpOnly": false,
|
||||||
|
"secure": false,
|
||||||
|
"sameSite": "Lax"
|
||||||
|
}
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user