Vinnaren i pepparkakshustävlingen!
2023-04-07, 17:18
  #1
Medlem
Kottkompotts avatar
Har en webscraper gjord på requests som håller på tugga genom någon miljon länkar.
Har två proxys jag byter mellan för olika syften, Stormproxies och Webshare.
På webshare verkar jag kunna köra 20 trådar på 100 roterande IP-adresser utan att bli bannad, och på Stormproxies finns det 700K adresser till hands, men jag har bara 10 trådar tillgängliga på abonnemanget.
Jag funderar på om det skulle gå att maximera användningen av de där 10 trådarna lite grann.

Jag försökte med något i stil med detta:
Kod:
max_concurrent_threads = 10
semaphore = threading.Semaphore(max_concurrent_threads)

# Define a function that uses the semaphore to control access to a shared resource
def worker():
    with semaphore:
        response = requests.get(url, proxies=proxies)
    # Ytterligare arbete som görs med scrapad data här, utanför semaphore

row_queue = Queue() # Lång kö av länkar som ska köras
for row in rows:
    row_queue.put(row)

num_threads = 50
# Create worker threads and start them
threads = []
for _ in range(num_threads):
    t = threading.Thread(target=worker, args=(row_queue, lock, database_file, len(rows)))
    t.start()
    threads.append(t)

for t in threads:
    t.join()

Tänkte mig något att om jag kör något i stil med 50 trådar eller fler, så kunde semaphore kanske begränsa dessa till att endast 10 åt gången har en öppen anslutning med requests, men så fort anslutningen är färdig så blir en slot ledig för en annan tråd att ta över.
Saknas förstås många bitar i ovanstående kod, men det bör visa översiktligt hur jag menar.
Syftet är lite att maximera bruket av 10 konkurrenta uppkopplingar, medans själva arbetet på scrapad data sker utanför begränsningen av antalet uppkopplingar, som man kan se på kommentaren i workerfunktionen.

Problemet är att när jag kör detta och kollar min internetanslutning så ser det konstigt ut.
Testade med 100 trådar och 10 semaphore bara för att, och det som hände var att absolut ingenting hände på typ 10 sekunder, sedan PANG så kom en topp på 20Mb/s användning i en halv sekund, och sedan ingenting i 10 sekunder igen, och sedan en topp, o.s.v.
Alltså blev det precis tvärtom till vad jag hade hoppats på, d.v.s. att det skulle bli en jämnare ström av konstanta uppkopplingar som löste av varandra.
__________________
Senast redigerad av Kottkompott 2023-04-07 kl. 17:37.
Citera
2023-04-07, 19:26
  #2
Medlem
Har du funderat på att använda thread pool istället för att hantera dem manuellt?

Kolla på ThreadPoolExecutor i concurrent.futures modulen. Trådarna kommer då hanteras automatiskt så du får en jämnare belastning.
Citera
2023-04-07, 23:55
  #3
Medlem
Citat:
Ursprungligen postat av Kottkompott

Problemet är att när jag kör detta och kollar min internetanslutning så ser det konstigt ut.
Testade med 100 trådar och 10 semaphore bara för att, och det som hände var att absolut ingenting hände på typ 10 sekunder, sedan PANG så kom en topp på 20Mb/s användning i en halv sekund, och sedan ingenting i 10 sekunder igen, och sedan en topp, o.s.v.
Alltså blev det precis tvärtom till vad jag hade hoppats på, d.v.s. att det skulle bli en jämnare ström av konstanta uppkopplingar som löste av varandra.

Nu är som du skriver inte din kod hel, men jag misstänker att du försöker koda multuthread i Python som om Python vore C#. En förklaring till din katchupeffekt är nog GIL.
Det som händer är att du har multitrådat I/O och CPU, som med din Queue gör så att Python försöker utföra en rad olika I/O på en gång, även om du har dem i multitrådar. Sedan gör den en rad olika CPU på en gång. Sedan släpper den lock på I/O och då sprutar den ut alla färdiga requests och du får ut 20mb/s på en gång. Sedan börjar den om och gör olika uppgifter och håller dig i 10 sekunder. Sedan släpper den lock igen och sprutar ut dina requests på en gång.

Testa att använda ansyncio (asyncio är det du nog ska sträva efter, då den planerar varje request) eller multiprocesses. Fungerar inte det kan du använda subprocesses (men det vill du nog helst slippa då du nu istället kommer att låsa datorn, då varje request nu öppnas som en ny pythoninstans!)
Citera
2023-04-10, 18:06
  #4
Medlem
Kottkompotts avatar
Citat:
Ursprungligen postat av Methos
Nu är som du skriver inte din kod hel, men jag misstänker att du försöker koda multuthread i Python som om Python vore C#. En förklaring till din katchupeffekt är nog GIL.
Det som händer är att du har multitrådat I/O och CPU, som med din Queue gör så att Python försöker utföra en rad olika I/O på en gång, även om du har dem i multitrådar. Sedan gör den en rad olika CPU på en gång. Sedan släpper den lock på I/O och då sprutar den ut alla färdiga requests och du får ut 20mb/s på en gång. Sedan börjar den om och gör olika uppgifter och håller dig i 10 sekunder. Sedan släpper den lock igen och sprutar ut dina requests på en gång.

Testa att använda ansyncio (asyncio är det du nog ska sträva efter, då den planerar varje request) eller multiprocesses. Fungerar inte det kan du använda subprocesses (men det vill du nog helst slippa då du nu istället kommer att låsa datorn, då varje request nu öppnas som en ny pythoninstans!)
Mycket bra läsning, det var lärorikt.

Tror jag väljer en annan väg istället nu, och poolar ihop så många fria proxies jag kan och hoppas på att åtminstone några fungerar.
Behåller några stycken på webshare så jag har något litet som alltid funkar.

Däremot får jag ändå inte upp hastigheten med att köra flera proxypooler, tvärtom verkar det gå saktare.

Testade det här (vilket är fulla koden för just den delen av programmet):
Kod:
def get_next_proxy(proxy_list):
    while True:
        for proxy in proxy_list:
            yield proxy

with ThreadPoolExecutor(max_workers=num_threads) as executor:
    futures = []

    proxy_webshare = '1.2.3.4:1234'
    proxy_stormproxies = '4.5.6.7.4567'

    # Load non-rotating proxies from JSON file
    with open('Free_Proxy_List.json', 'r') as f:
        proxy_data = json.load(f)

    non_rotating_proxies = [f"{proxy['ip']}:{proxy['port']}" for proxy in proxy_data]
    proxy_cycle = get_next_proxy(non_rotating_proxies)

    for _ in range(20):
        futures.append(executor.submit(full_update_worker, row_queue, lock, database_file, len(rows), proxy_webshare))

    for _ in range(10):
        futures.append(executor.submit(full_update_worker, row_queue, lock, database_file, len(rows), proxy_stormproxies))

    for _ in range(len(non_rotating_proxies)/4):
        proxy = next(proxy_cycle)
        futures.append(executor.submit(full_update_worker, row_queue, lock, database_file, len(rows), proxy))


    for future in as_completed(futures):
        future.result()

Jag måste vara ärlig, det var ett förslag jag fick efter ganska noga specifikationer till GPT-4, och jag tänker inte låtsas om att jag vet exakt vad det gör.
Men jag tror att det fungerar ungefär med att i varje for _ in range så lägger python till en mängd trådar som ska göras till futures[], och anger vilken proxy eller proxypool som ska användas för vardera tråd.

Såg nu dock att det kanske felar lite på tredje rangeiterationen, då det ser ut som att next(proxy_cycle) bara hämtar en ny IP-adress en enda gång, och sedan använder den hela tiden för den skapade tråden.
Fast det kan vara någon funktion i executor som jag inte förstår, som fixar det. Jag vet inte.
Jag körde dock inte detta program så länge att jag fick något absolut svar på hur lång uppskattningen blev. Har bakat in lite kod som uppskattar hur länge som är kvar baserad på tidigare tidsåtgång, men det tar kanske en halvtimme innan den regressat någorlunda nära en bra uppskattning.
Men initialt såg det ut som att det var på väg att ändå ta runt dubbelt så lång tid, fastän jag körde 100 trådar istället för 20.

Appropå det förresten, hur många trådar är övre gränsen man rimligen kan köra så här i python?
Har 250Mbit uppkoppling och 3700X, med en 5950X på ingång. Funderar på 500 eller 1000 Mbit bredband ifall jag lyckas köra riktigt många trådar.
Tänkte ifall jag lyckas hitta en massiv proxypool som funkar lite så där, men om det går köra någonstans norr om 1000 trådar och hoppas på att några av dem funkar dugligt iallafall (resten av programmet är felsäkrat med try, ifall enskilda requests felar).
Eller bör jag göra om programmet i C# om jag ska köra kopiösa mängder trådar?
Är ändå något jag tänkte köra typ varje månad (eller rentav varje vecka) för att uppdatera en databas med runt 1,5 miljoner rader.
Citera

Stöd Flashback

Flashback finansieras genom donationer från våra medlemmar och besökare. Det är med hjälp av dig vi kan fortsätta erbjuda en fri samhällsdebatt. Tack för ditt stöd!

Stöd Flashback