爬虫入门之数据的提取方法

数据的提取方法
介绍
前面的课程中,我们学习了如何发送发送,对应的,回顾之前的爬虫流程,在发送完请求之后,能够获取响应,这个时候就需要从响应中提取数据了

内容
数据提取的基础概念和数据分类
json模块的复习
正则表达式的复习
认识xml
xpath的学习
LXML类库的学习
多线程和多进程爬虫的学习

1.3.1数据提取的概念和数据的分类
目标
了解什么是数据提取
熟悉爬虫的数据的种类

  1. 什么是数据提取
    简单的来说,数据提取就是从响应中获取我们想要的数据的过程

  2. 爬虫中数据的分类
    结构化数据:json,xml等
    处理方式:直接转化为python类型
    非结构化数据:HTML
    处理方式:正则表达式、xpath
    下面以今日头条的首页为例,介绍结构化数据和非结构化数据

结构化数据例子:

非结构化数据:

XML数据:

<bookstore>
<book category="COOKING">
  <title lang="en">Everyday Italian</title> 
  <author>Giada De Laurentiis</author> 
  <year>2005</year> 
  <price>30.00</price> 
</book>
<book category="CHILDREN">
  <title lang="en">Harry Potter</title> 
  <author>J K. Rowling</author> 
  <year>2005</year> 
  <price>29.99</price> 
</book>
<book category="WEB">
  <title lang="en">Learning XML</title> 
  <author>Erik T. Ray</author> 
  <year>2003</year> 
  <price>39.95</price> 
</book>
</bookstore>

从上面可以看出,xml数据也是结构非常明显的

小结
本小结重点
理解数据提取的内涵
掌握爬虫中不同的数据分类

1.3.2数据提取之json
目标
理解json的概念
了解爬虫中json出现的位置
掌握json相关的方法
1.为什么要复习json
由于把json数据转化为python内建数据类型很简单,所以爬虫中,如果我们能够找到返回json数据的URL,就会尽量使用这种URL,而很多地方也都会返回json

  1. 什么是json
    JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,它使得人们很容易的进行阅读和编写。同时也方便了机器进行解析和生成。适用于进行数据交互的场景,比如网站前台与后台之间的数据交互。

3.哪里能找到返回json的url
下面以豆瓣热映电影为例,来了解那里能够找到返回json的url地址:https://movie.douban.com/cinema/nowplaying/beijing/

3.1 我们如何确定数据在哪里
在url地址对应的响应中搜索关键字即可

但是注意:url地址对应的响应中,中文往往是被编码之后的内容,所以更推荐大家去搜索英文和数字;另外一个方法就是在perview中搜索,其中的内容都是转码之后的

3.2 切换手机版寻找返回json的地址
在chrome中点击切换手机版的选项,需要重新刷新页面才能够切换成功,部分网站还需要重新进入主页面之后再继续点击才能够切换成功,比如:豆瓣热映

现在我们找到了返回电影数据的地址:https://m.douban.com/rexxar/api/v2/subject_collection/movie_showing/items?os=android&for_mobile=1&callback=jsonp1&start=0&count=18&loc_id=108288&_=1524495777522

通过请求我们发现,并不能成功,那是为什么呢?

对比抓包的地址和现在的地址,会发现部分headers字段没有,通过尝试,会发现是Referer字段缺少的原因

在上面这个地址中,我们会发现响应中包含部分数据并不是我们想要的,如下:

;jsonp1({"count": 18, "start": 0, "subject_collection_items": "...."})

其中jsonp1似乎是很眼熟,在请求的地址中包含一个参数callback=jsonp1,正是由于这个参数的存在,才导致结果中也会有这部分数据,对应的解决方法是:直接删除url地址中的callback字段即可,在url地址中很多字段都是没用的,比如这里的&loc_id,_等等,可以大胆尝试

3.3 json数据格式化方法
在preview中观察

其中:

红色方框部分表示json中的键
蓝色方框部分由于是个列表,展开后,下面的数字表示列表中对应位置的值
在线解析工具进行解析

比如:https://www.bejson.com/
pycharm进行reformat code

在pycharm中新建一个json文件,把数据存入后,点击code下面的reformat code,但是中文往往显示的是unicode格式
3.4 json数据的其他来源
抓包app,app的抓包方式会在后面学习,但是很多时候app中的数据是被加密的,但是仍然值得尝试

4.json模块中方法的学习

其中类文件对象的理解:

具有read()或者write()方法的对象就是类文件对象,比如f = open(“a.txt”,”r”) f就是类文件对象

具体使用方法:

#json.dumps 实现python类型转化为json字符串
#indent实现换行和空格
#ensure_ascii=False实现让中文写入的时候保持为中文
json_str = json.dumps(mydict,indent=2,ensure_ascii=False)


#json.loads 实现json字符串转化为python类型
my_dict = json.loads(json_str)


#json.dump 实现把python类型写入类文件对象
with open("temp.txt","w") as f:
    json.dump(mydict,f,ensure_ascii=False,indent=2)

# json.load 实现类文件对象中的json字符串转化为python类型
with open("temp.txt","r") as f:
    my_dict = json.load(f)
  1. json模块的作用

Json在数据交换中起到了一个载体的作用,承载相互传递的数据

  1. 练习
    爬取豆瓣电视剧上英剧和美剧两个分类的电视数据,地址:https://m.douban.com/tv/

小结
本小结重点
掌握json模块的用途
掌握寻找json的方法
理解json的概念

1.3.3数据提取之正则
目标
掌握正则表达式的常见语法
掌握re模块的常见用法
掌握原始字符串r的用法

  1. 什么是正则表达式
    用事先定义好的一些特定字符、及这些特定字符的组合,组成一个规则字符串,这个规则字符串用来表达对字符串的一种过滤逻辑。

  2. 正则表达式的常见语法
    知识点

正则中的字符
正则中的预定义字符集
正则中的数量词

正则的语法很多,不能够全部复习,对于其他的语法,可以临时查阅资料,比如:表示或还能使用|

练习: 下面的输出是什么?

string_a = '<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">\n\t\t<meta http-equiv="content-type" content="text/html;charset=utf-8">\n\t\t<meta content="always" name="referrer">\n        <meta name="theme-color" content="#2932e1">'
ret = re.findall("<.*>",string_a)
print(ret)
  1. re模块的常见方法
    pattern.match(从头找一个)
    pattern.search(找一个)
    pattern.findall(找所有)
    返回一个列表,没有就是空列表
    re.findall("\d",“chuan1zhi2”) >> [“1”,“2”]
    pattern.sub(替换)

re.sub("\d","",“chuan1zhi2”) >> ["chuan_zhi"]
re.compile(编译)

返回一个模型P,具有和re一样的方法,但是传递的参数不同
匹配模式需要传到compile中

p = re.compile("\d",re.S)
p.findall("chuan1zhi2")
  1. python中原始字符串r的用法
    知识点

原始字符串
元是字符串的长度
原始字符串的使用
原始字符串定义(raw string):所有的字符串都是直接按照字面的意思来使用,没有转义特殊或不能打印的字符,原始字符串往往针对特殊字符而言。例如"\n"的原始字符串就是"\n"

原始字符串的长度

  In [19]: len("\n")
  Out[19]: 1

  In [20]: len(r"\n")
  Out[20]: 2

  In [21]: r"\n"[0]
  Out[21]: '\\'

正则中原始字符串的使用

  In [13]: r"a\nb" == "a\\nb"
  Out[13]: True

  In [14]: re.findall("a\nb","a\nb")
  Out[14]: ['a\nb']

  In [15]: re.findall(r"a\nb","a\nb")
  Out[15]: ['a\nb']

  In [16]: re.findall("a\\nb","a\nb")
  Out[16]: ['a\nb']

  In [17]: re.findall("a\\nb","a\\nb")
  Out[17]: []

  In [18]: re.findall(r"a\\nb","a\\nb")
  Out[18]: ['a\\nb']

上面的现象说明什么?

正则中使用原始字符串r能够忽略转义符号带来的影响,加上原始字符串r之后,待匹配的字符串中有多少个\,正则中就添加多少个\即可

windows中原始字符串r的使用

  1. 练习
    re.compile该如何使用?
    如何非贪婪的去匹配内容?
    re.findall(r“a.bc”,”a\nbc”,re.DOTALL)和re.findall(r“a(.)bc”,”a\nbc”,re.DOTALL)的区别?
    不分组时匹配的是全部,分组后匹配的是组内的内容
  2. 动手
    通过正则匹配果壳问答上面的精彩回答的地址和标题:https://www.guokr.com/ask/highlight/?page=1 思路:

寻找url地址的规律

寻找数据的位置

获取36kr上的所有新闻:http://36kr.com/

小结
本小结重点
掌握正则表达式的常见语法
掌握re模块的常见用法
掌握原始字符串r的用法

1.3.4xpath和lxml类库
目标
了解xpath的定义
了解xml
掌握xpath的语法

  1. 为什么要学习xpath和lxml
    lxml是一款高性能的 Python HTML/XML 解析器,我们可以利用XPath,来快速的定位特定元素以及获取节点信息

  2. 什么是xpath
    XPath (XML Path Language) 是一门在 HTML\XML 文档中查找信息的语言,可用来在 HTML\XML 文档中对元素和属性进行遍历。

W3School官方文档:http://www.w3school.com.cn/xpath/index.asp

  1. 认识xml
    知识点:

html和xml的区别
xml中各个元素的的关系和属性
3.1 html和xml的区别

3.2 xml的树结构

<bookstore>
<book category="COOKING">
  <title lang="en">Everyday Italian</title> 
  <author>Giada De Laurentiis</author> 
  <year>2005</year> 
  <price>30.00</price> 
</book>
<book category="CHILDREN">
  <title lang="en">Harry Potter</title> 
  <author>J K. Rowling</author> 
  <year>2005</year> 
  <price>29.99</price> 
</book>
<book category="WEB">
  <title lang="en">Learning XML</title> 
  <author>Erik T. Ray</author> 
  <year>2003</year> 
  <price>39.95</price> 
</book>
</bookstore>

上面的xml内容可以表示为下面的树结构

上面的这种结构关系在xpath被进一步细化

  1. xpath的节点关系
    知识点:

认识xpath中的节点
了解xpath中节点之间的关系
4.1 xpath中的节点是什么
每个XML的标签我们都称之为节点,其中最顶层的节点称为根节点。

4.2 xpath中节点的关系

  1. xpath中节点选择的工具
    Chrome插件 XPath Helper
    下载地址:https://pan.baidu.com/s/1UM94dcwgus4SgECuoJ-Jcg 密码:337b
    Firefox插件 XPath Checker
    注意: 这些工具是用来学习xpath语法的,他们都是从elements中匹配数据,elements中的数据和url地址对应的响应不相同,所以在代码中,不建议使用这些工具进行数据的提取

  2. xpath语法
    知识点

掌握元素路径的相关方法
掌握获取获取属性的方法
掌握获取文本的方法
我们将在下面的例子中使用这个 XML 文档。

<bookstore>

<book>
  <title lang="eng">Harry Potter</title>
  <price>29.99</price>
</book>

<book>
  <title lang="eng">Learning XML</title>
  <price>39.95</price>
</book>

</bookstore>

6.1 选取节点
XPath 使用路径表达式来选取 XML 文档中的节点或者节点集。这些路径表达式和我们在常规的电脑文件系统中看到的表达式非常相似。

使用chrome插件选择标签时候,选中时,选中的标签会添加属性class=“xh-highlight”

下面列出了最有用的表达式:
表达式 描述
nodename 选中该元素。
/ 从根节点选取、或者是元素和元素间的过渡。
// 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。
. 选取当前节点。
… 选取当前节点的父节点。
@ 选取属性。
text() 选取文本。
实例
在下面的表格中,我们已列出了一些路径表达式以及表达式的结果:

路径表达式 结果
bookstore 选择bookstore元素。
/bookstore 选取根元素 bookstore。注释:假如路径起始于正斜杠( / ),则此路径始终代表到某元素的绝对路径!
bookstore/book 选取属于 bookstore 的子元素的所有 book 元素。
//book 选取所有 book 子元素,而不管它们在文档中的位置。
bookstore//book 选择属于 bookstore 元素的后代的所有 book 元素,而不管它们位于 bookstore 之下的什么位置。
//book/title/@lang 选择所有的book下面的title中的lang属性的值。
//book/title/text() 选择所有的book下面的title的文本。
练习:

接下来我们听过豆瓣电影top250的页面来练习上述语法:https://movie.douban.com/top250

选择所有的h1下的文本
//h1/text()
获取所有的a标签的href
//a/@href
获取html下的head下的title的文本
/html/head/title/text()
获取html下的head下的link标签的href
/html/head/link/@href
但是当我们需要选择所有的电影名称的时候会特别费力,通过下一小节的学习,就能够解决这个问题

6.2 查找特定的节点
路径表达式 结果
//title[@lang=“eng”] 选择lang属性值为eng的所有title元素
/bookstore/book[1] 选取属于 bookstore 子元素的第一个 book 元素。
/bookstore/book[last()] 选取属于 bookstore 子元素的最后一个 book 元素。
/bookstore/book[last()-1] 选取属于 bookstore 子元素的倒数第二个 book 元素。
/bookstore/book[position()>1] 选择bookstore下面的book元素,从第二个开始选择
//book/title[text()=‘Harry Potter’] 选择所有book下的title元素,仅仅选择文本为Harry Potter的title元素
/bookstore/book[price>35.00]/title 选取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值须大于 35.00。
注意点: 在xpath中,第一个元素的位置是1,最后一个元素的位置是last(),倒数第二个是last()-1

练习:选择所有的电影的名称,href,评分,评价人数

6.3 选取未知节点
XPath 通配符可用来选取未知的 XML 元素。

通配符 描述

  • 匹配任何元素节点。
    @* 匹配任何属性节点。
    node() 匹配任何类型的节点。
    实例
    在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:

路径表达式 结果
/bookstore/* 选取 bookstore 元素的所有子元素。
//* 选取文档中的所有元素。
//title[@*] 选取所有带有属性的 title 元素。
6.3 选取若干路径
通过在路径表达式中使用“|”运算符,您可以选取若干个路径。

实例
在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:

路径表达式 结果
//book/title | //book/price 选取 book 元素的所有 title 和 price 元素。
//title | //price 选取文档中的所有 title 和 price 元素。
/bookstore/book/title | //price 选取属于 bookstore 元素的 book 元素的所有 title 元素,以及文档中所有的 price 元素。
小结
本小结重点

1.3.5理解xml和html的区别
掌握xpath的使用
其他

在xpath 的语法中大家只需要掌握\,@,text() 的使用就掌握了xpath 80%的技巧
xpath helper工具不建议在写爬虫的时候使用

lxml模块的学习
目标
掌握lxml的使用
熟悉lxml对数据处理和提取之后的数据类型

  1. lxml的认识
    在前面学习了xpath的语法,那么在代码中我们如何使用xpath呢,对应的我们需要lxml

安装方式:pip install lxml

  1. lxml的使用
    2.1 lxml模块的入门使用
    导入lxml 的 etree 库 (导入没有提示不代表不能用)

    from lxml import etree

利用etree.HTML,将字符串转化为Element对象,Element对象具有xpath的方法,返回结果的列表,能够接受bytes类型的数据和str类型的数据

html = etree.HTML(text) 
ret_list = html.xpath("xpath字符串")

把转化后的element对象转化为字符串,返回bytes类型结果 etree.tostring(element)

假设我们现有如下的html字符换,尝试对他进行操作

<div> <ul> 
<li class="item-1"><a href="link1.html">first item</a></li> 
<li class="item-1"><a href="link2.html">second item</a></li> 
<li class="item-inactive"><a href="link3.html">third item</a></li> 
<li class="item-1"><a href="link4.html">fourth item</a></li> 
<li class="item-0"><a href="link5.html">fifth item</a> # 注意,此处缺少一个 </li> 闭合标签 
</ul> </div>

from lxml import etree
text = ''' <div> <ul> 
        <li class="item-1"><a href="link1.html">first item</a></li> 
        <li class="item-1"><a href="link2.html">second item</a></li> 
        <li class="item-inactive"><a href="link3.html">third item</a></li> 
        <li class="item-1"><a href="link4.html">fourth item</a></li> 
        <li class="item-0"><a href="link5.html">fifth item</a> 
        </ul> </div> '''

html = etree.HTML(text)
print(type(html)) 

handeled_html_str = etree.tostring(html).decode()
print(handeled_html_str)

输出为

<class 'lxml.etree._Element'>
<html><body><div> <ul> 
        <li class="item-1"><a href="link1.html">first item</a></li> 
        <li class="item-1"><a href="link2.html">second item</a></li> 
        <li class="item-inactive"><a href="link3.html">third item</a></li> 
        <li class="item-1"><a href="link4.html">fourth item</a></li> 
        <li class="item-0"><a href="link5.html">fifth item</a> 
        </li></ul> </div> </body></html>

可以发现,lxml确实能够把确实的标签补充完成,但是请注意lxml是人写的,很多时候由于网页不够规范,或者是lxml的bug,即使参考url地址对应的响应去提取数据,任然获取不到,这个时候我们需要使用etree.tostring的方法,观察etree到底把html转化成了什么样子,即根据转化后的html字符串去进行数据的提取。

2.2 lxml的深入练习
接下来我们继续操作,假设每个class为item-1的li标签是1条新闻数据,如何把这条新闻数据组成一个字典

from lxml import etree
text = ''' <div> <ul> 
        <li class="item-1"><a href="link1.html">first item</a></li> 
        <li class="item-1"><a href="link2.html">second item</a></li> 
        <li class="item-inactive"><a href="link3.html">third item</a></li> 
        <li class="item-1"><a href="link4.html">fourth item</a></li> 
        <li class="item-0"><a href="link5.html">fifth item</a> 
        </ul> </div> '''

html = etree.HTML(text)

#获取href的列表和title的列表
href_list = html.xpath("//li[@class='item-1']/a/@href")
title_list = html.xpath("//li[@class='item-1']/a/text()")

#组装成字典
for href in href_list:
    item = {}
    item["href"] = href
    item["title"] = title_list[href_list.index(href)]
    print(item)

输出为

{'href': 'link1.html', 'title': 'first item'}
{'href': 'link2.html', 'title': 'second item'}
{'href': 'link4.html', 'title': 'fourth item'}
假设在某种情况下,某个新闻的href没有,那么会怎样呢?

from lxml import etree
text = ''' <div> <ul> 
        <li class="item-1"><a>first item</a></li> 
        <li class="item-1"><a href="link2.html">second item</a></li> 
        <li class="item-inactive"><a href="link3.html">third item</a></li> 
        <li class="item-1"><a href="link4.html">fourth item</a></li> 
        <li class="item-0"><a href="link5.html">fifth item</a> 
        </ul> </div> '''

结果是

{'href': 'link2.html', 'title': 'first item'}
{'href': 'link4.html', 'title': 'second item'}

数据的对应全部错了,这不是我们想要的,接下来通过2.3小节的学习来解决这个问题

2.3 lxml模块的进阶使用
前面我们取到属性,或者是文本的时候,返回字符串

但是如果我们取到的是一个节点,返回的是element对象,可以继续使用xpath方法,对此我们可以在后面的数据提取过程中:先根据某个标签进行分组,分组之后再进行数据的提取

示例如下:

from lxml import etree
text = ''' <div> <ul> 
        <li class="item-1"><a>first item</a></li> 
        <li class="item-1"><a href="link2.html">second item</a></li> 
        <li class="item-inactive"><a href="link3.html">third item</a></li> 
        <li class="item-1"><a href="link4.html">fourth item</a></li> 
        <li class="item-0"><a href="link5.html">fifth item</a> 
        </ul> </div> '''

html = etree.HTML(text)

li_list = html.xpath("//li[@class='item-1']")
print(li_list)

结果为:

[<Element li at 0x11106cb48>, <Element li at 0x11106cb88>, <Element li at 0x11106cbc8>]

可以发现结果是一个element对象,这个对象能够继续使用xpath方法

先根据li标签进行分组,之后再进行数据的提取

from lxml import etree
text = ''' <div> <ul> 
        <li class="item-1"><a>first item</a></li> 
        <li class="item-1"><a href="link2.html">second item</a></li> 
        <li class="item-inactive"><a href="link3.html">third item</a></li> 
        <li class="item-1"><a href="link4.html">fourth item</a></li> 
        <li class="item-0"><a href="link5.html">fifth item</a> 
        </ul> </div> '''

#根据li标签进行分组
html = etree.HTML(text)
li_list = html.xpath("//li[@class='item-1']")

#在每一组中继续进行数据的提取
for li in li_list:
    item = {}
    item["href"] = li.xpath("./a/@href")[0] if len(li.xpath("./a/@href"))>0 else None
    item["title"] = li.xpath("./a/text()")[0] if len(li.xpath("./a/text()"))>0 else None
    print(item)

结果是:

{'href': None, 'title': 'first item'}
{'href': 'link2.html', 'title': 'second item'}
{'href': 'link4.html', 'title': 'fourth item'}

前面的代码中,进行数据提取需要判断,可能某些一面不存在数据的情况,对应的可以使用三元运算符来解决

以上提取数据的方式:先分组再提取,都会是我们进行数据的提取的主要方法

  1. 动手
    用XPath来做一个简单的爬虫,爬取某个贴吧里的所有帖子,获取每个帖子的标题,连接和帖子中图片

思路分析:

推荐使用极速版的页面,响应不包含js,elements和url地址对应的响应一样

获取所有的列表页的数据即连接和标题

2.1. 确定url地址,确定程序停止的条件

url地址的数量不固定,不能够去构造url列表,需要手动获取下一页的url地址进行翻页

有下一页的情况:

没有下一页的情况:

2.2. 确定列表页数据的位置

由于没有js,可以直接从elements中进行数据的提取

获取帖子中的所有数据

3.1 确定url地址

url详情页的规律和列表页相似

3.2 确定数据的位置

小结
本小结重点
熟悉lxml对数据处理和提取之后的数据类型
掌握lxml的使用
掌握lxml数据提取的方法:先分组再提取

1.3.6更快的爬虫实现
目标
掌握多线程爬虫
掌握多进程爬虫

  1. 爬取糗事百科段子
    页面的URL是:http://www.qiushibaike.com/8hr/page/1

思路分析:

确定url地址

url地址的规律非常明显,一共只有13页url地址

确定数据的位置

数据都在id='content-left’的div下的div中,在这个区域,url地址对应的响应和elements相同

  1. 上述代码改写成多线程方式实现
    2.1 回顾多线程的方法使用
    在python3中,主线程主进程结束,子线程,子进程不会结束

为了能够让主线程回收子线程,可以把子线程设置为守护线程,即该线程不重要,主线程结束,子线程结束

t1 = threading.Thread(targe=func,args=(,))
t1.setDaemon(True)
t1.start() #此时线程才会启动

2.2 回顾队列模块的使用

from queue import Queue
q = Queue(maxsize=100)
item = {}
q.put_nowait(item) #不等待直接放,队列满的时候会报错
q.put(item) #放入数据,队列满的时候回等待
q.get_nowait() #不等待直接取,队列空的时候会报错
q.get() #取出数据,队列为空的时候会等待
q.qsize() #获取队列中现存数据的个数 
q.join() #队列中维持了一个计数,计数不为0时候让主线程阻塞等待,队列计数为0的时候才会继续往后执行
q.task_done() 
# put的时候计数+1,get不会-1,get需要和task_done 一起使用才会-1

2.3 多线程实现思路剖析
把爬虫中的每个步骤封装成函数,分别用线程去执行
不同的函数通过队列相互通信,函数间解耦
3. 将上述代码改写成多进程方式实现
3.1 回顾多进程程的方法使用

from multiprocessing import Process
t1 = Process(targe=func,args=(,))
t1.daemon = True  #设置为守护进程
t1.start() #此时线程才会启动

3.2 多进程中队列的使用
多进程中使用普通的队列模块会发生阻塞,对应的需要使用multiprocessing提供的JoinableQueue模块,其使用过程和在线程中使用的queue方法相同

小结
本小结重点
能够通过多线程多进程实现爬虫
掌握queue中put和get以及task_done和join方法的使用

1.3.7通过线程池实现更快的爬虫
目标
掌握线程池的使用
掌握协程池的使用

  1. 线程池使用方法介绍
    实例化线程池对象

    from multiprocessing.dummy import Pool
    pool = Pool(process=5) #默认大小是cup的个数

把从发送请求,提取数据,到保存合并成一个函数,交给线程池异步执行

使用方法pool.apply_async(func)

 def exetute_requests_item_save(self):
     url = self.queue.get()
     html_str = self.parse_url(url)
     content_list = self.get_content_list(html_str)
     self.save_content_list(content_list)
     self.total_response_num +=1

 pool.apply_async(self.exetute_requests_item_save)

添加回调函数

通过apply_async的方法能够让函数异步执行,但是只能够执行一次

为了让其能够被反复执行,通过添加回调函数的方式能够让_callback 递归的调用自己

同时需要指定递归退出的条件

 def _callback(self,temp):
     if self.is_running:
          pool.apply_async(self.exetute_requests_item_save,callback=self._callback)

 pool.apply_async(self.exetute_requests_item_save,callback=self._callback)

确定程序结束的条件 程序在获取的响应和url数量相同的时候可以结束

 while True: #防止主线程结束
     time.sleep(0.0001)  #避免cpu空转,浪费资源
     if self.total_response_num>=self.total_requests_num:
         self.is_running= False
         break
 self.pool.close() #关闭线程池,防止新的线程开启
# self.pool.join() #等待所有的子线程结束
  1. 使用线程池实现爬虫的具体实现

    coding=utf-8

    import requests
    from lxml import etree
    from queue import Queue
    from multiprocessing.dummy import Pool
    import time

    class QiubaiSpider:
    def init(self):
    self.url_temp = “https://www.qiushibaike.com/8hr/page/{}/”
    self.headers = {“User-Agent”: “Mozilla/5.0 (Macintosh; Intel Mac OS X
    10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36”}
    self.queue = Queue()
    self.pool = Pool(5)
    self.is_running = True
    self.total_requests_num = 0
    self.total_response_num = 0

     def get_url_list(self):  # 获取url列表
         for i in range(1, 14):
             self.queue.put(self.url_temp.format(i))
             self.total_requests_num += 1
    
     def parse_url(self, url):  # 发送请求,获取响应
         return requests.get(url, headers=self.headers).content.decode()
    
     def get_content_list(self, html_str):  # 提取段子
         html = etree.HTML(html_str)
         div_list = html.xpath("//div[@id='content-left']/div")
         content_list = []
         for div in div_list:
             content = {}
             content["content"] = div.xpath(".//div[@class='content']/span/text()")
             print(content)
             content_list.append(content)
         return content_list
    
     def save_content_list(self, content_list):  # 保存数据
         pass
    
     def exetute_requests_item_save(self):
         url = self.queue.get()
         html_str = self.parse_url(url)
         content_list = self.get_content_list(html_str)
         self.save_content_list(content_list)
         self.total_response_num += 1
    
     def _callback(self, temp):
         if self.is_running:
             self.pool.apply_async(self.exetute_requests_item_save, callback=self._callback)
    
     def run(self):
         self.get_url_list()
    
         for i in range(2):  # 控制并发
             self.pool.apply_async(self.exetute_requests_item_save, callback=self._callback)
    
         while True:  # 防止主线程结束
             time.sleep(0.0001)  # 避免cpu空转,浪费资源
             if self.total_response_num >= self.total_requests_num:
                 self.is_running = False
                 break
    
         self.pool.close()  # 关闭线程池,防止新的线程开启
         # self.pool.join() #等待所有的子线程结束
    

    if name == ‘main’:
    qiubai = QiubaiSpider()
    qiubai.run()

  2. 使用协程池实现爬虫的具体实现

    coding=utf-8

    import gevent.monky
    gevent.monky.path_all()
    from gevent.pool import Pool

    import requests
    from lxml import etree
    from queue import Queue
    import time

    class QiubaiSpider:
    def init(self):
    self.url_temp = “https://www.qiushibaike.com/8hr/page/{}/”
    self.headers = {“User-Agent”: “Mozilla/5.0 (Macintosh; Intel Mac OS X
    10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36”}
    self.queue = Queue()
    self.pool = Pool(5)
    self.is_running = True
    self.total_requests_num = 0
    self.total_response_num = 0

     def get_url_list(self):  # 获取url列表
         for i in range(1, 14):
             self.queue.put(self.url_temp.format(i))
             self.total_requests_num += 1
    
     def parse_url(self, url):  # 发送请求,获取响应
         return requests.get(url, headers=self.headers).content.decode()
    
     def get_content_list(self, html_str):  # 提取段子
         html = etree.HTML(html_str)
         div_list = html.xpath("//div[@id='content-left']/div")
         content_list = []
         for div in div_list:
             content = {}
             content["content"] = div.xpath(".//div[@class='content']/span/text()")
             print(content)
             content_list.append(content)
         return content_list
    
     def save_content_list(self, content_list):  # 保存数据
         pass
    
     def exetute_requests_item_save(self):
         url = self.queue.get()
         html_str = self.parse_url(url)
         content_list = self.get_content_list(html_str)
         self.save_content_list(content_list)
         self.total_response_num += 1
    
     def _callback(self, temp):
         if self.is_running:
             self.pool.apply_async(self.exetute_requests_item_save, callback=self._callback)
    
     def run(self):
         self.get_url_list()
    
         for i in range(2):  # 控制并发
             self.pool.apply_async(self.exetute_requests_item_save, callback=self._callback)
    
         while True:  # 防止主线程结束
             time.sleep(0.0001)  # 避免cpu空转,浪费资源
             if self.total_response_num >= self.total_requests_num:
                 self.is_running = False
                 break
    

    if name == ‘main’:
    qiubai = QiubaiSpider()
    qiubai.run()

小结
本小结重点
掌握线程池的使用方法和流程
掌握协程池的方式实现爬虫

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值