Python代理刮刀/检查器添加多线程问题

Python代理刮刀/检查器添加多线程问题,python,multithreading,Python,Multithreading,我已经设法拼凑了一个代理刮板/检查器,它确实可以工作,但速度相当慢。我听说添加线程可以加快进程,这已经超出了我的能力,我想知道是否有人可以帮助我演示如何在代码中实现线程。我读到python附带了线程库,我曾尝试添加它,但它似乎创建了第二个线程,执行完全相同的操作,因此它只是在保存重复项的同时浏览相同的代理列表。这是代码 import requests from bs4 import BeautifulSoup from random import choice import threading

我已经设法拼凑了一个代理刮板/检查器,它确实可以工作,但速度相当慢。我听说添加线程可以加快进程,这已经超出了我的能力,我想知道是否有人可以帮助我演示如何在代码中实现线程。我读到python附带了线程库,我曾尝试添加它,但它似乎创建了第二个线程,执行完全相同的操作,因此它只是在保存重复项的同时浏览相同的代理列表。这是代码

import requests
from bs4 import BeautifulSoup
from random import choice
import threading
import time
    
stop_flag = 0
    
def get_proxies():
    link = 'https://api.proxyscrape.com/?request=displayproxies&proxytype=all&timeout=5000&country=all&anonymity=all&ssl=no'
    other = 'https://www.proxy-list.download/api/v1/get?type=http'
    get_list1 = requests.get(link).text
    get_list2 = requests.get(other).text
    soup1 = BeautifulSoup(get_list1, 'lxml')
    soup2 = BeautifulSoup(get_list2, 'lxml')
    list1 = soup1.find('body').get_text().strip()
    list2 = soup2.find('body').get_text().strip()
    mix = list1+'\n'+list2+'\n'
    raw_proxies = mix.splitlines()
    t = threading.Thread(target=check_proxy, args=(raw_proxies,))
    t.start()
    time.sleep(0.5)
    return check_proxy(raw_proxies)

def check_proxy(proxies):
    check = 'http://icanhazip.com'       
    for line in proxies:

        headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0","Accept-Encoding": "*","Connection": "keep-alive"}
        try:    
            response = requests.get(check, proxies={'http': 'http://'+line}, headers=headers, timeout=5)
            status = response.status_code

            outfile = open('good_proxies.txt', 'a')
            if status is 200:
                print('good - '+line)
                outfile.write(line+'\n')
            else:
        
                pass
        except Exception:
            print('bad  - '+line)
    outfile.close()        

get_proxies()
下面的代码应该运行得更快。最好是在主线程中完成所有文件写入和打印,并让工作线程简单地返回结果:

import requests
from bs4 import BeautifulSoup
from random import choice
import threading
from concurrent.futures import ThreadPoolExecutor, as_completed
from functools import partial

stop_flag = 0

def get_list(session, url):
    get_list = session.get(url).text
    soup = BeautifulSoup(get_list, 'lxml')
    return soup.find('body').get_text().strip()


def get_proxies(session, executor):
    link = 'https://api.proxyscrape.com/?request=displayproxies&proxytype=all&timeout=5000&country=all&anonymity=all&ssl=no'
    other = 'https://www.proxy-list.download/api/v1/get?type=http'
    lists = list(executor.map(partial(get_list, session), (link, other)))
    mix = lists[0] + '\n' + lists[1] + '\n'
    raw_proxies = mix.splitlines()
    with open('good_proxies.txt', 'a') as outfile:
        headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0","Accept-Encoding": "*","Connection": "keep-alive"}
        session.headers.update(headers)
        futures = {executor.submit(partial(check_proxy, session), proxy): proxy for proxy in raw_proxies}
        for future in as_completed(futures):
            proxy = futures[future]
            is_good = future.result()
            if is_good:
                print('good -', proxy)
                outfile.write(proxy + '\n')
            else:
                print('bad -', proxy)


def check_proxy(session, proxy):
    check = 'http://icanhazip.com'
    try:
        response = session.get(check, proxies={'http': 'http://'+proxy}, timeout=5)
        status = response.status_code
        return status == 200
    except Exception:
        return False


N_THREADS=100
with requests.Session() as session:
    with ThreadPoolExecutor(max_workers=N_THREADS) as executor:
        get_proxies(session, executor)
部分输出:

bad - 104.40.158.173:80
bad - 47.105.149.144:3128
good - 185.220.115.150:80
bad - 138.197.157.32:8080
bad - 138.197.157.32:3128
good - 116.17.102.174:3128
good - 183.238.173.226:3128
good - 119.8.44.244:8080
good - 1.174.138.125:8080
good - 116.17.102.131:3128
good - 101.133.167.140:8888
good - 118.31.225.11:8000
good - 117.131.119.116:80
good - 101.200.127.78:80
good - 1.70.67.175:9999
good - 116.196.85.150:3128
good - 1.70.64.160:9999
bad - 102.129.249.120:3128
bad - 138.68.41.90:3128
bad - 138.68.240.218:3128
good - 47.106.239.215:3328
good - 183.162.158.172:4216
bad - 138.68.240.218:8080
good - 115.219.131.244:3000
bad - 138.68.161.14:3128
good - 185.49.107.1:8080
bad - 134.209.29.120:8080
解释

速度的提高主要是通过线程实现的,其次是通过
requests
包提供的,其主要优点是,如果您向同一主机发出多个请求,那么相同的TCP连接将被重用

Python提供了两种线程池机制,(1)未记录的
multiprocessing.pool.ThreadPool
类,该类与用于创建子进程池的
multiprocessing.pool.pool
类共享同一接口,以及(2)来自
concurrent.futures
模块的
ThreadPoolExecutor
类,它与来自同一模块的
ProcessPoolExecutor
类共享同一接口,该类用于创建处理器池。此代码使用
ThreadPoolExecutor

线程是轻量级的,创建起来相对便宜,一台典型的台式计算机可以支持几千个线程。然而,一个给定的应用程序,取决于它正在做什么,通过创建超出某个最大值的更多线程,可能不会获利。线程只适用于非CPU密集型的“作业”。也就是说,它们经常放弃CPU以允许其他线程运行,因为它们正在等待I/O操作或URL get请求完成。这是因为Python字节码不能在多个线程中并行运行,因为Python解释器在执行字节码之前获得全局解释器锁(GIL)

创建
ThreadPoolExecutor
实例(分配给变量
executor
),使用
max\u workers
参数指定池中的线程数。这里我相当随意地指定了100个线程。您可以尝试增加这个值,看看它是否可以提高性能。
ThreadPoolExecutor
实例有两种方法可用于将“作业”或“任务”提交到线程池执行。看见函数
map
类似于内置的
map
函数,因为它返回一个迭代器,将函数应用于iterable结果的每个项,从而生成结果。不同之处在于,现在将通过将每个调用作为“作业”提交到线程池来并发进行函数调用。帮助构建
raw_proxy
列表的函数是
get_list
,它负责检索单个URL:

def get_list(session, url):
    get_list = session.get(url).text
    soup = BeautifulSoup(get_list, 'lxml')
    return soup.find('body').get_text().strip()
现在我想为每个URL同时调用这个函数,因此我想使用
map
函数,其中iterable参数是URL列表。问题是,
map
将只向worker函数传递一个参数(每次调用的iterable的每个元素),但我还想传递
session
参数。我本可以将
会话
变量分配给全局变量,但还有另一种方法
functools.partial(get_list,session)
创建另一个函数,调用该函数时,其行为就像调用
get_list
时第一个参数“硬编码”为
session
,因此我在调用
map
时使用此新函数:

lists = list(executor.map(partial(get_list, session), (link, other)))
我将调用
map
返回的iterable转换为
列表
,以后可以对其进行索引

将作业提交到线程池的另一种方法称为
submit
。它将worker函数和worker函数的参数作为参数,并立即返回
未来
的实例,而无需等待作业完成。有多种方法可应用于此
未来的
实例,最重要的方法是
结果
,它会一直阻止,直到作业完成并从worker函数返回返回值。我可以很容易地再次使用
map
函数,将
raw_proxies
作为iterable参数传递,然后迭代调用
map
的返回值。但我会按照作业提交的顺序(即它们在
raw_proxies
列表中出现的顺序)阻止作业。这可能还不算太糟糕,因为在所有“作业”都完成之前,程序不会完成。但是,如果您不需要按特定顺序输出结果,那么在作业完成后立即处理结果(独立于其提交顺序)会稍微更高效一些。
submit
函数返回一个
Future
实例,提供了灵活性:

我单独将每个代理IP作为一个单独的作业提交,并将生成的
Future
作为密钥存储在字典中,其值是用于创建作业的IP值。我使用字典理解的一个语句来完成这一切:

futures = {executor.submit(partial(check_proxy, session), proxy): proxy for proxy in raw_proxies}
然后,我使用另一种方法,
concurrent.futures
as_completed
,迭代字典的所有键值,即futures,在每个
Future
实例完成时返回它们,然后查询
Future
作业的返回值,这将
    for future in as_completed(futures):
        proxy = futures[future]
        is_good = future.result()
        if is_good:
            print('good -', proxy)
            outfile.write(proxy + '\n')
        else:
            print('bad -', proxy)