在日常爬虫工作中,我们有时候会使用单线程或多线程,单线程和多线程进行数据抓取结果还是大有不同的。当单线程python爬虫已经不能满足企业需求时,很多程序员会进行改代码或者增加服务器数量,这样虽说也能达到效果,但是对于人力物力也是一笔不小的消耗。如果是技术牛点的,正常都会自己重新改写多线程代码来实现海量数据的获取。但是要值得注意的事,如果多线程没调配好可能连单线程的效率都比不上。本次就和大家一起聊一聊单线程多线程的一些需要注意的事项。
知识点
线程也叫轻量级进程,是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属的一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤销另一个线程,同一进程中的多个线程之间可以并发执行。这里简单的举例下单线程和多线程之间的差别
单线程
``import time
def task(url):
s = url.split('_')[-1]
time.sleep(int(s)) #这里模拟请求等待
urls = ['url_1', 'url_2', 'url_3']
start = time.time()
for url in urls:
task(url)
end = time.time()
print(end - start)
6.013520002365112
多线程
import threading
import time
def task(url):
s = url.split('_')[-1]
time.sleep(int(s))
ts = []
urls = ['url_1', 'url_2', 'url_3']
start = time.time()
for url in urls:
t = threading.Thread(target=task, args=(url,))
t.start()
ts.append(t)
for t in ts:
t.join()
end = time.time()
print(end - start)
# 3.005527973175049
这时候我们就能看到多线程的优势了,虽然多线程只是在各线程来回切换,但是可以让IO堵塞的时间切换到其他线程做其他的任务,很适合爬虫或者文件的操作。接下来我们通过python实现豆瓣网采集,增加多线程处理,同时对豆瓣返回的内容进行分类统计
```import asyncio
import aiohttp
import threading
from collections import Counter
# 定义一个全局变量,用于存储分类结果
categories = Counter()
# 定义一个函数,用于根据文本内容进行分类
def classify(text):
# 这里可以使用任何文本分类的方法,例如正则表达式、机器学习等
# 这里为了简单起见,只使用了简单的字符串匹配
if "Python" in text:
return "Python"
elif "Java" in text:
return "Java"
elif "C++" in text:
return "C++"
else:
return "Other"
async def fetch_page(url, proxy):
# 创建一个 aiohttp 的 ClientSession 对象,并指定代理IP和端口
async with aiohttp.ClientSession(proxy=proxy) as session:
# 使用 session.get 方法发送请求,并获取响应对象
async with session.get(url) as response:
# 返回响应的文本内容
return await response.text()
async def main():
urls = ["https://www.douban.com//s?wd=" + str(i) for i in range(10)] # 生成十个豆瓣搜索网址
# 假设有一个文件 16yun.txt,每行存储一个代理host和端口,例如 www.16yun.cn:3333
# 读取文件中的所有代理,并存储在一个列表中
with open("16yun.txt") as f:
proxies = [line.strip() for line in f]
tasks = [] # 创建一个空列表,用于存储 task 对象
# 遍历 urls 和 proxies 列表,为每个 url 配对一个 proxy,并创建 task 对象
for url, proxy in zip(urls, proxies):
task = asyncio.create_task(fetch_page(url, proxy))
tasks.append(task)
results = await asyncio.gather(*tasks) # 同时运行所有 task 并获取结果
# 创建一个线程池,用于执行分类任务
pool = threading.ThreadPoolExecutor(max_workers=4)
for result in results:
print(result[:100]) # 打印每个网页的前 100 个字符
# 使用线程池提交一个分类任务,并更新全局变量 categories
category = pool.submit(classify, result).result()
categories[category] += 1
# 关闭线程池并等待所有任务完成
pool.shutdown(wait=True)
# 打印最终的分类结果
print(categories)
asyncio.run(main()) # 运行主协程