0


python爬虫的学习总结

背景

基于django框架完成jira网页数据的爬取。由于对爬虫知识知道的太少,我开始了新的学习之旅。本文前半部分都是记录这一周主要的错误,如果想直接看最终成果,可以跳到本文“成功爬取”部分浏览。

学习爬虫知识

在知道了本项目可能需要爬虫后,我开始学习爬虫相关知识,通过对爬虫教程的阅读实践修改,我初步理解了一些相关的语法:

  1. 什么时候可以使用爬虫:但且仅当符合robots.txt时可以。
  2. 以get方式爬取:request.get(url),返回response,使用response.text输出html文件
  3. 以post方式爬取:调用开发者模式,选取network,查找来往文件,取出url和formdata中的内容组成新的字典,request.post(url,formdata),并用json解析,即可获得数据。
  4. 问题:原文中的链接已经不让爬取了,get的我是用baidu,post可以参考其他文章,将url中的_o去掉或者实时生成参数。

至此爬虫学习部分就暂时告一段落,截图就不放了,真正的困难还在后面

了解jira

jira,Atlassian公司出品的项目与事务跟踪工具,被广泛应用于缺陷跟踪、客户服务、需求收集、流程审批、任务跟踪、项目跟踪和敏捷管理等工作领域。我司主要用于测试提单的提交工作,但提单的数量十分庞大,一个一个查询十分不方便,因此我的任务就是爬取网站数据,并进行分析,存入数据库,并在我们自己的网页上更清晰明了的完成显示。

对API的尝试

和同事探讨了解jira后,我知道了jira有自己的API,只需要替换成我司的网站就可以爬取数据了,但很可惜,经过两天的尝试,除了几个API可以返回结果,其余的都是返回404或者json解析错误。没法得到数据。基于之前学习的一些爬虫知识,我开始转向打算使用正常原始的爬虫方法爬取数据,也就是本文的重点,也是一切问题的开始。。。
注:另一位成员则继续学习相关API,并发现python有更好封装的jira库,这个库已经证实可以爬取该网站,并且效率方面更优(因为正常爬取需要一条数据一条数据的爬取,自然不如直接查询数据库获得全部数据来的快,但基于本文方法更为通用,易于后人爬取其他网站,因此我们两个决定同步完成爬取代码的编写)

原始方法爬取

最开始的代码由于失败,已经找不到了,这也是一个教训吧,以后写代码的时候会注意按版本顺序编写,而不是失败后直接就覆盖掉。现在凭借记忆完成描述吧。
首先遇到的一大困难就是jira网站需要登录,这也是后续诸多问题的三大核心来源之一。直接爬取会出现直接跳转到登录界面,而无法获取信息。由于原始爬取的方法记忆性较差,因此在学习了cookie之后,我开始尝试使用cookie进行登录,参考模拟登录这篇文章,在真实登录过网站后,在开发者模式下选择network,查找来往文件,提取头部含有cookie的文件,复制cookie和user_agent,并将他们加入头部,很顺利的让我进入网站,获取了相关信息。但是好景不长,在第二天关机重启后,我再次运行,却发现cookie已经发生了改变,这种方式无法实现长久的登录,最终也只好放弃。(我后来也想过每次模拟登录一下获取cookie然后就可以了,但当时我已经知道了selenium,就没有费事搞了)
另一个核心问题就是jira网站的加载方式问题,访问首页时浏览器可以看到提单列表和提单内容,但爬虫爬不到这些数据,经过后期诸多尝试猜测,我认为这是由于加载顺序问题造成的,实际上,jira是分许多个文件依次加载各个模块,而一般原始爬虫代码在接受到一个response后就不会继续接受response了,也不会去拼接这些网站,所以无法爬到完整的数据,因为虽然以提单的id爬虫可以获取到对应的数据,但我却无法很好的获取我要爬的链表。也是因为如此,我开始新的一轮搜索,尝试找到如何应对这种多个response的情况,知道我在知乎上意外看到了selenium这个单词。

学习selenium

selenium是一个完全仿真用户真实运行的工具,可以完成链接的点击,文本框输入,读取数据,甚至拖拽等动作。如此岂不美哉嘛。那我还何必担心会出现登录问题,我直接代码帮我登录就好了嘛。
说干就干,我用两分钟扫了一眼别人的博客,复制运行一下代码完成简单的学习使用。这里我主要参考的是selenium实例,同时根据错误方法进行修改,符合我的环境的语法。有一个不太好的地方就是网上资源大都是针对Chrome或者火狐的,当然Edge经过实践也是完全可以用的,只不过参数需要自己调一下。比如它的options是要用

from selenium.webdriver.edge.options import Options
options = Options()
browser=webdriver.Edge(options = options)

很快我的第一个登录程序就写完了,这个代码还能找回来,就隐去一些信息放出来吧。

  1. try:
  2. browser=webdriver.Edge(options=options)
  3. browser.get("要访问的网站")
  4. input1=browser.find_element(By.ID,value="login-form-username")
  5. input2=browser.find_element(By.ID,value="login-form-password")
  6. input1.send_keys("your_user_name")
  7. input2.send_keys("your_password")
  8. button=browser.find_element(By.ID,value="login-form-submit")
  9. button.click()
  10. browser.implicitly_wait(10)print(browser.find_element(By.XPATH,'要爬取数据的XPATH路径').text)
  11. lis = browser.find_elements(By.CLASS_NAME,'splitview-issue-link')finally:
  12. browser.close()

就这样我分别通过id,class,xpath完成登录并精准获取第一个提单的提交信息和创建时间,但与此同时,我一直不敢面对的第三大核心问题也随之出现了。。。怎么访问近两个月的数据? 相信有人肯定会说直接爬嘛,都知道提单列表了。但别忘了,两个月的提单,就单纯我们组就有数十个,一个一个爬取起码要一两分钟,绝对不可取的。另一个同学用jira API可以直接一次获得,我真实爬取却要依次进行,当然啦,其实也就第一次爬要这么久,以后都爬变化的就好了。但我还是抱有一丝侥幸,觉得多线程可以解决我的问题,在加上我还没有写过python的多线程,所以何不就此学学呢?哪知一入佛门深似海,让我仿佛一切都回到了原点。

多线程加速

最开始我决定参考这篇selenium多线程加速,于是改动代码先开一个线程去访问链表中的一个元素试试,但随后发现一个让我很不爽的问题,由于我要多线程操作多个界面,所以肯定是要开多个新的窗口去访问,可谁知开新的窗口居然不能保存登录状态,直接跳转到登录链接,这可不好啊,如果每次都要登录岂不是太过麻烦(可以开几个线程登录上,然后这几个线程分布访问一部分数据,当然这是后话,我当时想的首先就是保存登录信息)。那这个简单,认证我是不是登录过只需要我的cookie对就可以了嘛,因此我就在网上搜索添加cookie的方法。但不知道是版本原因还是什么情况,加上cookie依然不可以自动登录。我又尝试勾选浏览器自带的记住本电脑,但依然无果。我猜想可能是因为记住本电脑也只是去记录cookie,并不是记录电脑的信息,所以开新的网页时依然会发生要输入密码的问题。

保存浏览器信息

首先搜索的是这篇博客保存浏览器信息,亲测有效果但对于多线程很不友好,每次只能开一个窗口新打开的窗口覆盖原来的,如果开多线程的话会发生多个线程访问的是一个数据,但对于开多个端口的想法测试过,但目前还没有尝试完整,但只是在代码中开多线程启用cmd运行是不对的,最后会发生链接不上,用Telnet测试也是如此,只有某一个端口可以找到。
注:这里补充一些python运行cmd的知识,使用

import os
os.system(your_cmd)

这里要注意的是如果出现路径带有空格,cmd解析会出现问题,必须要把路径用引号引起来才可以。

成功爬取

经过一周的试错,我最终选择了一个目前来看最优的结果。首先创建若干个线程,让每个线程登录,并最终获取列表,在列表中给每个线程分配相同数量的链接任务,并各自打开对应链接的标签(如果是窗口会需要再次登录)。最后获取需要的数据。话不多说,上代码(本代码为安全保密起见,增加了一些数据的隐藏(所有中文部分),无法运行,主要是为了看思路,关键步骤已经增加了注释)

  1. # -*- coding: utf-8 -*-from selenium import webdriver
  2. from selenium.webdriver.common.by import By
  3. from selenium.webdriver.common.keys import Keys
  4. from selenium.webdriver.support import expected_conditions as EC
  5. from selenium.webdriver.support.wait import WebDriverWait
  6. from selenium.webdriver.edge.options import Options
  7. from selenium.webdriver.edge.service import Service
  8. from threading import Thread
  9. import subprocess,threading,time
  10. import time,sys,os,copy
  11. thread_list =[]
  12. process_list =[]
  13. thread_num =2
  14. process_num =2
  15. last_herf ="最后要爬取的数据(链接),也可以改变stop去停止程序运行"
  16. stop =0
  17. create_list =[]
  18. lock = threading.RLock()#互斥锁defcreate(href,browser):
  19. href ='window.open("'+ href +'")'print(href)
  20. browser.execute_script(href)#打开新标签
  21. browser.implicitly_wait(10)
  22. browser.switch_to.window(browser.window_handles[-1])
  23. temp = browser.find_element(By.XPATH,'要获取信息的路径').text
  24. print(temp)
  25. lock.acquire()
  26. create_list.append(temp)#保存
  27. lock.release()
  28. browser.switch_to.window(browser.window_handles[0])defget_jira(num):try:
  29. options = Options()# 处理SSL证书错误问题
  30. options.add_argument('--ignore-certificate-errors')
  31. options.add_argument('--ignore-ssl-errors')# 忽略无用的日志
  32. options.add_experimental_option("excludeSwitches",['enable-automation','enable-logging'])
  33. options.add_argument('--no-sandbox')
  34. options.add_argument('--disable-dev-shm-usage')
  35. options.add_argument('--headless')#无窗口模式运行
  36. options.add_argument('blink-settings=imagesEnabled=false')
  37. options.add_argument('--disable-gpu')
  38. browser=webdriver.Edge(options=options)
  39. browser.get("你首先要访问的网站(登录网站)")
  40. browser.implicitly_wait(10)
  41. input1=browser.find_element(By.ID,value="用户名标签")
  42. input2=browser.find_element(By.ID,value="密码标签")
  43. input1.send_keys("你的用户名")
  44. input2.send_keys("你的密码")
  45. button=browser.find_element(By.ID,value="登录标签")
  46. button.click()
  47. browser.implicitly_wait(10)
  48. button=browser.find_element(By.XPATH,value='获取所有信息链接的路径')
  49. button.click()#跳转以进入根网页
  50. browser.implicitly_wait(10)# print_log = open("printlog.html","w")# sys.stdout = print_log# print(browser.page_source)
  51. lis = browser.find_elements(By.CLASS_NAME,'获取所有信息形成的链表')#链表用elementsfor i inrange(len(lis)):# print(lis[i].get_attribute("href"),num)if i%thread_num == num:#等额分配任务
  52. create(lis[i].get_attribute("href"),browser)if lis[i].get_attribute("href")== last_herf or stop:breakfinally:
  53. browser.close()defstart_prog():for i inrange(thread_num):#创建线程
  54. t = Thread(target=get_jira,args=(i,))
  55. t.start()
  56. thread_list.append(t)for i in thread_list:
  57. i.join()if __name__ =='__main__':
  58. time_start = time.time()
  59. start_process()for i in create_list:print(i)
  60. time_end = time.time()
  61. time_c = time_end - time_start
  62. print('time_cost', time_c,'s')

实际运行

经过我司网站实际爬取的成果显示,爬取32条数据时,使用2-4线程(进程)效果较好,这是因为虽然开多线程(进程)对IO密集型程序来说可以很好的提高速度,但是由于开多线程(进程)时,每次都需要登录(这是我最想取消的一步,但很可惜,始终没有找到一个完美的解决方案)会浪费大量时间。而且根据测试结果得出,jira服务器可能对多线程(进程)实现的不是很优良,以至于我在开多线程时,访问时间并不会明显缩短,相比于单线程,测试表现中,双线程可以节约1/3的时间,四线程可以节约接近1/2时间,8线程可以节约略多于1/2的时间。但开4线程以上的线程时,登录跳转时间已经可以占总运行时间一半以上。因此2-4线程时加速效果比较好,可以加速30%左右。当然,在爬取数据更多时,比如爬取100个数据,得到的结果是:相比于单线程,测试表现中,相比于单线程,双线程可以节约1/2时间,4线程可以节约2/3时间,6线程可以节约5/7时间,8线程可以节约约5/7时间。再计算进入登录过程的时间,可以得出6线程的性能略好于4线程,但性能差异不大。因此使用4-6线程对于爬取100数据来说效果较好。
综上所述,jira对于多线程(进程)有一定的实现,但效果不佳,存在瓶颈上限,当浏览线程数超过2时,性能提升度逐渐下降,4线程并不能达到4倍的提升速率,超过8时,浏览器无法正常运行。在我打开任务管理器进行监控时发现,2线程时的CPU占有率已经达到80%左右。之所以后期有所继续提升,应该是因为本程序介于IO密集型和CPU密集型程序吧。

进一步提速

由于爬虫最后是为了获取数据的,那么加载图片、css和JavaScript等用处就不是很大了,可以参考博客selenium加速爬取来完成加速,实测加速效果可以提速约1.2倍的样子。
当然这还没有结束,由于网页时分层加载的,所以不需要像上面代码所写的使用隐式等待,等到所有html都加载完毕才获取数据,可以使用显式等待。至此,爬虫的爬取速率已经得到了尽可能大的提升。

反思

本次项目至此已经完成了爬取核心部分,做一个简单的移植即可在已有框架中实现了。整个过程充满了跌宕起伏,但也学到了许多东西。对后来人也是一个很好的模板(例如多线程,多进程的使用,以后爬取时可以直接更换网页和对应的查找元素即可)。

怕什么真理无穷,进一寸有一寸欢喜。

标签: python 爬虫 学习

本文转载自: https://blog.csdn.net/weixin_46238869/article/details/125914438
版权归原作者 weixin_46238869 所有, 如有侵权,请联系我们删除。

“python爬虫的学习总结”的评论:

还没有评论