进程: 运行中的程序. 每次我们执行一个程序, 咱们的操作系统对自动的为这个程序准备一些必要的资源(例如, 分配内存, 创建一个能够执行的线程. )
线程: 程序内, 可以直接被CPU调度的执行过程. 是操作系统能够进行运算调度的最小单位. 它被包含在进程之中, 是进程中的实际运作单位.
进程与线程之间的关系:
进程是资源单位. 线程是执行单位. 就好比是一家公司. 一家公司的资源就是桌椅板凳, 电脑饮水机这些资源, 但是, 我们如果说一家公司正在运转着, 运行着. 那里面必须要有能为这家公司工作的人. 程序里面也一样, 进程就是为了程序运行而需要的各种资源. 但是程序想要运行, 就必须由线程来被CPU调度执行.
我们运行的每一个程序默认都会有一个线程. 哪怕是只有helloworld级别的程序. 想要执行. 也会有一个线程产生.
顾名思义, 多线程就是让程序产生多个线程一起去执行. 还拿公司举例子. 一家公司里如果只有一个员工, 工作效率肯定不会高到哪里去. 怎么提高效率? 多招点儿人就OK了.
如何实现多线程, 在python中, 有两种方案实现多线程.
我们先看看单线程的效果
def func():
for i in range(1000):
print("func", i)
if __name__ == '__main__':
func()
for i in range(1000):
print("main", i)
再看多线程
from threading import Thread
def func():
for i in range(1000):
print("func", i)
if __name__ == '__main__':
t = Thread(target=func)
t.start()
for i in range(1000):
print("main", i)
from threading import Thread
class MyThread(Thread):
def run(self):
for i in range(1000):
print("func", i)
if __name__ == '__main__':
t = MyThread()
t.start()
for i in range(1000):
print("main", i)
以上两种是最基本的python创建多线程的方案. python还提供了线程池
python还提供了线程池功能. 可以一次性的创建多个线程, 并且, 不需要我们程序员手动去维护. 一切都交给线程池来自动管理.
# 线程池
def fn(name):
for i in range(1000):
print(name, i)
if __name__ == '__main__':
with ThreadPoolExecutor(10) as t:
for i in range(100):
t.submit(fn, name=f"线程{i}")
如果任务有返回值怎么办?
def func(name):
time.sleep(2)
return name
def do_callback(res):
print(res.result())
if __name__ == '__main__':
with ThreadPoolExecutor(10) as t:
names = ["线程1", "线程2", "线程3"]
for name in names:
# 方案一, 添加回调
t.submit(func, name).add_done_callback(do_callback)
if __name__ == '__main__':
start = time.time()
with ThreadPoolExecutor(10) as t:
names = [5, 2, 3]
# 方案二, 直接用map进行任务分发. 最后统一返回结果
results = t.map(func, names, ) # 结果是按照你传递的顺序来执行的, 代价就是如果第一个没结束. 后面就都没结果
for r in results:
print("result", r)
print(time.time() - start)
http://www.xinfadi.com.cn/marketanalysis/0/list/1.shtml
依然用新发地这个案例.
import requests
from lxml import etree
from concurrent.futures import ThreadPoolExecutor
def get_page_source(url):
resp = requests.get(url)
return resp.text
def get_totle_count():
url = "http://www.xinfadi.com.cn/marketanalysis/0/list/1.shtml"
source = get_page_source(url)
tree = etree.HTML(source)
last_href = tree.xpath("//div[@class='manu']/a[last()]/@href")[0]
totle = last_href.split("/")[-1].split(".")[0]
return int(totle)
def download_content(url):
source = get_page_source(url)
tree = etree.HTML(source)
trs = tree.xpath("//table[@class='hq_table']/tr[position() > 1]")
result = []
for tr in trs:
tds = tr.xpath("./td/text()")
result.append((tds[0], tds[1], tds[2], tds[3], tds[4], tds[5], tds[6]))
return result
def main():
f = open("data.csv", mode="w")
totle = get_totle_count()
url_tpl = "http://www.xinfadi.com.cn/marketanalysis/0/list/{}.shtml"
with ThreadPoolExecutor(50) as t:
data = t.map(download_content, (url_tpl.format(i) for i in range(1, totle+1)))
# 拿到所有任务的返回
for item in data:
# 每个任务的数据循环出一行
for detial in item:
# 写入文件
content = ",".join(detial) + "\n"
print(content)
f.write(content)
if __name__ == '__main__':
main()
一个公司能创造的价值毕竟是有限的. 怎么办? 开分公司啊. 此所谓多进程. python实现多进程的方案和多线程几乎一样. 非常的简单
###1. 直接用Process创建进程
def func():
for i in range(1000):
print("func", i)
if __name__ == '__main__':
p = Process(target=func)
p.start()
for i in range(1000):
print("main", i)
class MyProcess(Process):
def run(self):
for i in range(1000):
print("MyProcess", i)
if __name__ == '__main__':
t = MyProcess()
t.start()
for i in range(1000):
print("main", i)
###3.多进程在爬虫中的应用
我们一般很少直接使用多进程. 最适合使用多进程的情况是: 多个任务需要一起执行. 并且互相之间数据可能有交汇但功能相对独立.比如, 我们自己做一个代理IP池, 就需要从网络上进行抓取, 抓取得到的IP要进行校验才可以进行使用. 此时, 抓取任务和校验任务就相当于完全独立的两个功能. 此时就可以启动多个进程来实现. 再比如, 如果遇到图片抓取的时候, 我们知道图片在一般都在网页的img标签中src属性存放的是图片的下载地址. 此时我们可以采用多进程的方案来实现, 一个负责疯狂扫图片下载地址. 另一个进程只负责下载图片.
综上, 多个任务需要并行执行, 但是任务之间相对独立(不一定完全独立). 可以考虑用多进程.
# 进程1. 从图片网站中提取到图片的下载路径
def get_pic_src(q):
print("start main page spider")
url = "http://www.591mm.com/mntt/"
resp = requests.get(url)
tree = etree.HTML(resp.text)
child_hrefs = tree.xpath("//div[@class='MeinvTuPianBox']/ul/li/a/@href")
print("get hrefs from main page", child_hrefs)
for href in child_hrefs:
href = parse.urljoin(url, href)
print("handle href", href)
resp_child = requests.get(href)
tree = etree.HTML(resp_child.text)
pic_src = tree.xpath("//div[@id='picBody']//img/@src")[0]
print(f"put {pic_src} to the queue")
q.put(pic_src)
# 作业, 分页图片抓取
# print("ready to another!")
# others = tree.xpath('//ul[@class="articleV2Page"]')
# if others:
# 进程2. 从图片网站中提取到图片的下载路径
def download(url):
print("start download", url)
name = url.split("/")[-1]
resp = requests.get(url)
with open(name, mode="wb") as f:
f.write(resp.content)
resp.close()
print("downloaded", url)
def start_download(q):
with ThreadPoolExecutor(20) as t:
while True:
t.submit(download, q.get()) # 启动
def main():
q = Queue()
p1 = Process(target=start_download, args=(q,))
p2 = Process(target=get_pic_src, args=(q,))
p1.start()
p2.start()
if __name__ == '__main__':
main()