这几天在忙一个爬虫程序,一直在改进他,从一开始的单线程,好几秒一张图片(网络不好),,,到现在每秒钟十几张图片,,, 四个小时586万条数据,,,简直不要太爽 先上图

  

  最终写出来的程序,线程数已经可以动态调整了,贼暴力。。。峰值能稳定在50个线程,具体思路可以继续看

  这里终于用到了操作系统的知识,就是生产者和消费者的模型。。。(参考源码忘记记录了,抱歉

  先简单说一下目标网站的情况,目标网站是一个图片网站,有一个列表页,点进列表页之后,可以看到很多图片,这只爬虫的目的是收集这些图片链接(有了链接当然也能下载了...

  简单分析之后发现,在列表页,会向后台请求一个json格式的数据文件,然后js动态的把里面的id组合成一个链接,最终组成如下样式的链接

    

  1. http://www.xxxxxx.com/photo/json?page=1977

  显而易见,page参数就是指定页数的,那么,这里就可以先生成一个列表,用for循环把所有列表页的url加进去,接下来只需要遍历这个链接列表就好了。

  

  1. #首先构造产品队列
  2. for i in range(1,11613):
  3. url_list.append("http://www.xxxxxx.com/photo/json?page="+str(i));
  4. print('产生链接完成');

  接下来,就是启动生产者线程,通过列表里的url,获取到每一个详情页的id,进而拼接出详情页的url,接下来把生产的详情页url添加到一个任务队列里面就好了,这就是生产者的工作。

  

  1. #生产者
  2. def producer(url_list,in_queue):
  3. print('进入生产者线程');
  4. global flag;
  5. for url in url_list:
  6. html = open_page(url); #获取总页json 得到每一个页的id 进而得到每个页的url
  7. if html == '':
  8. continue;
  9. else:
  10. idurl_list = get_idurl(html); #得到第n页的所有详情页url
  11. if len(idurl_list)==0: #如果取不到url 直接进行下一页
  12. continue;
  13. for idurl in idurl_list:
  14. in_queue.put(idurl);
  15. #print('生产完成一个');
  16. flag=1;
  17. print('产品生产完成');

  接着,需要在等待几秒钟,让生产者先生产一些产品。

  然后创建一个管理消费者的线程,能够创建新的消费者线程

  1. #线程管理线程
  2. consumer_thread = Thread(target=manger_thread,args=(in_queue,));
  3. consumer_thread.daemon = True;
  4. consumer_thread.start();

  线程里面的代码是这样的

  

  1. def manger_thread(in_queue):
  2. global thread_num;
  3. while True:
  4. if in_queue.qsize()>3000 and thread_num<80: #设置最大线程80
  5. consumer_thread = Thread(target=consumer,args=(in_queue,));
  6. consumer_thread.daemon = True;
  7. consumer_thread.start();
  8. thread_num+=1;

  简单解释一下,有一个全局变量,thread_num 这个就是用来调整进程数的依据,始终为消费者数目。

  接着,创建一个死循环,不停的判断任务队列中的产品数量,超过3000个,并且现在线程数小于80个,那就创建一个消费者线程。

  消费者代码:

  

  1. #消费者
  2. def consumer(in_queue):
  3. global count;
  4. global flag;
  5. global thread_num;
  6. print('进入消费者线程,队列长度: '+str(in_queue.qsize()));
  7. while True:
  8. if in_queue.qsize()<3000 and thread_num>10: #队列中数量小于5000 并且线程数大于10 就取消一个线程
  9. thread_num-=1;
  10. return;
  11. html = open_page(in_queue.get()); #取得一个详情页链接开始取得源码
  12. if html == '': #获取源码失败
  13. in_queue.task_done(); #虽然打开网页失败了 但是似乎还是得确认完成
  14. continue;
  15. image_url = get_url(html); #得到详情页图片url列表
  16. save_url(image_url); #保存链接
  17. #print('队列长度: '+str(in_queue.qsize()));
  18. count+=1;
  19. os.system('title '+'已爬组数:'+str(count)+'_队列长度:'+str(in_queue.qsize())+'_线程数:'+str(thread_num));
  20. in_queue.task_done();

  首先声明的几个全局变量是用来显示各种参数的

  这里依旧是一个死循环,循环中判断 任务队列中产品数量小于3000并且线程数大于10的话,那就退出这个线程。 通过线程管理线程以及这里的调整,队列长度稳定在3000

  然后打开网页源码,解析图片链接即可。

  值得一提的是,直接获取那个网页的源码,并不能得到图片的链接,需要对连接中字符串进行替换,,,具体怎么替换,需要查看js代码,然后用python源码实现一遍就好。

  下面放出所有的源码(要注意,代码中所有url全部都是修改了的,所以代码不能直接运行,,,如果想让他运行起来,可以私信我,或者留言给我

  

  1. #encoding:utf-8
  2. import bs4;
  3. import urllib.request;
  4. import urllib.error; # abc
  5. from urllib.request import urlretrieve
  6. import time;
  7. import os;
  8. import json;
  9. from queue import Queue;
  10. import threading;
  11. from retrying import retry;
  12. from threading import Thread;
  13.  
  14. count = 0; #记录组数
  15. thread_num = 0; #线程数
  16. flag = 0; #生产者完成标志
  17.  
  18. #打开网页 直接返回源码
  19. @retry(wait_fixed=1000,stop_max_attempt_number=50) #异常重试
  20. def open_page(url):
  21. print('打开网页: '+url);
  22. header = {};
  23. header['User-Agent'] = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36";
  24. req = urllib.request.Request(url,headers=header);
  25. response = urllib.request.urlopen(req,timeout=5);
  26. #times=str(time.time());
  27. #print('读取内容'+times);
  28. temp = response.read().decode('utf-8');
  29. #print('读取结束'+times);
  30. return temp;
  31.  
  32. #保存url
  33. def save_url(url_list): #考虑到追加字符串比较频繁 所以组合成一个大的字符串一起写入可以降低磁盘I/O (大概?
  34. #print('保存url: '+str(len(url_list)));
  35. file_handle = open('list.txt','a+'); #不存在文件就创建并且追加
  36. url_lists=[url+'\n' for url in url_list]; #添加回车
  37. file_handle.writelines(url_lists);
  38. file_handle.close();
  39.  
  40. #从详细页中得到照片的url 返回列表
  41. def get_url(html):
  42. image_url=[];
  43. try:
  44. if len(html)==0: #传参是空的
  45. return [];
  46. soup = bs4.BeautifulSoup(html,'html.parser'); #解析html代码
  47. for img in soup.find_all('img'): #这个循环用来得到
  48. if 'type3' in img['data-avaurl']:
  49. str2 = img['data-avaurl'].replace('type3','https://xxxxxx9.com');
  50. if 'type4' in img['data-avaurl']:
  51. str2 = img['data-avaurl'].replace('type4','https://xxxxxx4.com');
  52. if 'type5' in img['data-avaurl']:
  53. str2 = img['data-avaurl'].replace('type5','https://xxxxxx1.com');
  54. image_url.append(str2);
  55. except Exception as e: #返回空列表
  56. print('发生错误: '+e);
  57. return [];
  58. return image_url;
  59.  
  60. #得到每个id 对应的详情页url 返回列表
  61. def get_idurl(html):
  62. idurl_list=[];
  63. if len(html)==0: #传参为空 直接返回
  64. return [];
  65. for item in json.loads(html)['data']['items']:
  66. idurl_list.append("http://www.xxxxxxx.com/photo/show?id="+str(item['id'])); #获取到每一页的url
  67. return idurl_list;
  68.  
  69. #生产者
  70. def producer(url_list,in_queue):
  71. print('进入生产者线程');
  72. global flag;
  73. for url in url_list:
  74. html = open_page(url); #获取总页json 得到每一个页的id 进而得到每个页的url
  75. if html == '':
  76. continue;
  77. else:
  78. idurl_list = get_idurl(html); #得到第n页的所有详情页url
  79. if len(idurl_list)==0: #如果取不到url 直接进行下一页
  80. continue;
  81. for idurl in idurl_list:
  82. in_queue.put(idurl);
  83. #print('生产完成一个');
  84. flag=1;
  85. print('产品生产完成');
  86.  
  87. #消费者
  88. def consumer(in_queue):
  89. global count;
  90. global flag;
  91. global thread_num;
  92. print('进入消费者线程,队列长度: '+str(in_queue.qsize()));
  93. while True:
  94. if in_queue.qsize()<3000 and thread_num>10: #队列中数量小于5000 并且线程数大于10 就取消一个线程
  95. thread_num-=1;
  96. return;
  97. html = open_page(in_queue.get()); #取得一个详情页链接开始取得源码
  98. if html == '': #获取源码失败
  99. in_queue.task_done(); #虽然打开网页失败了 但是似乎还是得确认完成
  100. continue;
  101. image_url = get_url(html); #得到详情页图片url列表
  102. save_url(image_url); #保存链接
  103. #print('队列长度: '+str(in_queue.qsize()));
  104. count+=1;
  105. os.system('title '+'已爬组数:'+str(count)+'_队列长度:'+str(in_queue.qsize())+'_线程数:'+str(thread_num));
  106. in_queue.task_done();
  107.  
  108. def manger_thread(in_queue):
  109. global thread_num;
  110. while True:
  111. if in_queue.qsize()>3000 and thread_num<80: #设置最大线程80
  112. consumer_thread = Thread(target=consumer,args=(in_queue,));
  113. consumer_thread.daemon = True;
  114. consumer_thread.start();
  115. thread_num+=1;
  116.  
  117. if __name__=='__main__':
  118. start_time = time.time();
  119. url_list = []; #构造的产品集合
  120. in_queue = Queue(); #次级产品队列
  121. queue = Queue(); #线程队列
  122.  
  123. #首先构造产品队列
  124. for i in range(1,11613):
  125. url_list.append("http://www.xxxxxxx.com/photo/json?page="+str(i));
  126. print('产生链接完成');
  127.  
  128. producer_thread = Thread(target=producer,args=(url_list,in_queue,)); #创建生产者线程
  129. producer_thread.daemon = True; #设置为守护线程,主线程不退出,子线程也不退出
  130. producer_thread.start(); #启动生产者线程,生产url
  131.  
  132. time.sleep(15);
  133.  
  134. #线程管理线程
  135. consumer_thread = Thread(target=manger_thread,args=(in_queue,));
  136. consumer_thread.daemon = True;
  137. consumer_thread.start();
  138.  
  139. in_queue.join(); #阻塞,直到所有的次级产品消耗完毕
  140. print('所有产品消费完成,花费时间: '+str(time.time()-start_time)+'已爬组数: '+count);
  141. exit();

  因为我自己也是才开始写爬虫的原因,上面的代码很粗糙,,,但是我发誓,我有用心写。

  代码的缺点也很明显,就是不停的销毁线程,创建线程很耗费资源,,,这里需要改进,也许需要使用线程池(我的服务器CPU满载了,惊喜的是 网络页满载了,意味着,基本上速度最快了(带宽瓶颈

  动态调整线程的原因是因为,列表页的服务器和详情页图片的服务器不一样,这就意味着有时候任务队列中任务很多,有时候消费者又会饿着,浪费时间。

  还有就是,这次的目标网站几乎没有反爬措施(如果详情页图片链接需要替换不算反爬措施),,, 所以很顺利,也能很暴力 但是更多的网站都是有反爬的。。。需要混合代理服务器

  需要运行代码调试学习交流的朋友请在评论区留言或者发私信

  希望能帮助大家,更希望有大佬指导 谢谢 ^ _ ^

动态调整线程数的python爬虫代码分享的更多相关文章

  1. python爬虫代码

    原创python爬虫代码 主要用到urllib2.BeautifulSoup模块 #encoding=utf-8 import re import requests import urllib2 im ...

  2. Dubbo入门到精通学习笔记(十一):Dubbo服务启动依赖检查、Dubbo负载均衡策略、Dubbo线程模型(结合Linux线程数限制配置的实战分享)

    文章目录 Dubbo服务启动依赖检查 Dubbo负载均衡策略 Dubbo线程模型(结合Linux线程数限制配置的实战分享) 实战经验分享( ** 属用性能调优**): Dubbo服务启动依赖检查 Du ...

  3. JMeter命令行方式运行时动态设置线程数及其他属性(动态传参)

    在使用JMeter进行性能测试时,以下情况经常出现: 1.测试过程中,指定运行的线程数.指定运行循环次数不断改变: 2.访问的目标地址发生改变,端口发生改变,需要改写脚本. 上面的问题在GUI中,直接 ...

  4. 【数量技术宅 | Python爬虫系列分享】实时监控股市重大公告的Python爬虫

    实时监控股市重大公告的Python爬虫小技巧 精力有限的我们,如何更加有效率地监控信息? 很多时候特别是交易时,我们需要想办法监控一些信息,比如股市的公告.如果现有的软件没有办法实现我们的需求,那么就 ...

  5. 我不就是吃点肉,应该没事吧——爬取一座城市里的烤肉店数据(附完整Python爬虫代码)

    写在前面的一点屁话: 对于肉食主义者,吃肉简直幸福感爆棚!特别是烤肉,看着一块块肉慢慢变熟,听着烤盘上"滋滋"的声响,这种期待感是任何其他食物都无法带来的.如果说甜点是" ...

  6. 爬取汽车之家新闻图片的python爬虫代码

    import requestsfrom bs4 import BeautifulSouprespone=requests.get('https://www.autohome.com.cn/news/' ...

  7. 动态线程池(DynamicTp)之动态调整Tomcat、Jetty、Undertow线程池参数篇

    大家好,这篇文章我们来介绍下动态线程池框架(DynamicTp)的adapter模块,上篇文章也大概介绍过了,该模块主要是用来适配一些第三方组件的线程池管理,让第三方组件内置的线程池也能享受到动态参数 ...

  8. 【图文详解】python爬虫实战——5分钟做个图片自动下载器

    python爬虫实战——图片自动下载器 之前介绍了那么多基本知识[Python爬虫]入门知识,(没看的先去看!!)大家也估计手痒了.想要实际做个小东西来看看,毕竟: talk is cheap sho ...

  9. [Python爬虫]使用Selenium操作浏览器订购火车票

    这个专题主要说的是Python在爬虫方面的应用,包括爬取和处理部分 [Python爬虫]使用Python爬取动态网页-腾讯动漫(Selenium) [Python爬虫]使用Python爬取静态网页-斗 ...

随机推荐

  1. centos yum 安装openresty

    yum 安装openresty sudo yum install yum-utils -y sudo yum-config-manager --add-repo https://openresty.o ...

  2. 安装 centos8.1

    阿里云镜像下载链接 http://mirrors.aliyun.com/centos/8.1.1911/isos/x86_64/ 选择 CentOS-8.1.1911-x86_64-dvd1.iso ...

  3. [HNOI2003] 消防局的设立 - 树形dp

    仍然是点覆盖集问题,但覆盖半径变成了\(2\) 延续上一题的思路,只是式子更加复杂了 想体验一下min_element大法于是不想优化了 #include <bits/stdc++.h> ...

  4. vue报错 [Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's

    [Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent c ...

  5. lnmp1.5一键安装包安装lnmpa后,添加站点

    lnmp1.5一键安装包安装lnmpa后,添加站点 (1)添加站点 (2)配置apache配置文件 在/usr/local/apache/conf/vhost文件夹下,修改webApp站点配置文件ap ...

  6. 【Unity|C#】基础篇(10)——泛型(Generic)/ 泛型约束条件(where)

    [学习资料] <C#图解教程>(第17章):https://www.cnblogs.com/moonache/p/7687551.html 电子书下载:https://pan.baidu. ...

  7. 3ds Max File Format (Part 3: The department of redundancy department; Config)

    Now we'll have a look at the Config stream. It begins like follows, and goes on forever with various ...

  8. HTML的文档设置标记

    1.格式标记 <br/> 强制换行标记 <p> 换段落标记 换段落,由于多个空格和回车在HTML中会被等效为一个空格,所以HTML中要换段落就要用<p>,<p ...

  9. Codeforces Round #600 (Div. 2) B. Silly Mistake

    #include<iostream> #include<map> #include<set> #include<algorithm> using nam ...

  10. testclass面试题

    http://www.testclass.net/interview/selenium/   seleniuim面试题 http://www.testclass.net/interview/inter ...