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