页面解析和数据提取
一般来讲对我们而言,爬虫需要抓取的是某个网站或者某个应用的内容,提取有用的数据。响应内容一般分为两种,非结构化的数据 和 结构化的数据。
结构化数据:先有结构、再有数据非结构化数据:先有数据,再有结构,不同类型的数据,我们需要采用不同的方式来处理。实际上爬虫一共就四个主要步骤:
明确目标 (要知道你准备在哪个范围或者网站去搜索)爬 (将所有的网站的内容全部爬下来)取 (去掉对我们没用处的数据)处理数据(按照我们想要的方式存储和使用)我们在昨天的案例里实际上省略了第3步,也就是"取"的步骤。因为我们down下了的数据是全部的网页,这些数据很庞大并且很混乱,大部分的东西使我们不关心的,因此我们需要将之按我们的需要过滤和匹配出来。
那么对于文本的过滤或者规则的匹配,最强大的就是正则表达式,是Python爬虫世界里必不可少的神兵利器。
正则表达式,又称规则表达式,通常被用来检索、替换那些符合某个模式(规则)的文本。 正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。
给定一个正则表达式和另一个字符串,我们可以达到如下的目的:
给定的字符串是否符合正则表达式的过滤逻辑(“匹配”);通过正则表达式,从文本字符串中获取我们想要的特定部分(“过滤”)。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VgL7Zf3W-1572593860656)(assets/re01.png)]
在 Python 中,我们可以使用内置的 re 模块来使用正则表达式。
有一点需要特别注意的是,正则表达式使用 对特殊字符进行转义,所以如果我们要使用原始字符串,只需加一个 r 前缀,示例:
r'zhongguo\t\.\tpython'compile 函数
compile 函数用于编译正则表达式,生成一个 Pattern 对象,它的一般使用形式如下:
import re # 将正则表达式编译成 Pattern 对象 pattern = re.compile(r'\d+')在上面,我们已将一个正则表达式编译成 Pattern 对象,接下来,我们就可以利用 pattern 的一系列方法对文本进行匹配查找了。
Pattern 对象的一些常用方法主要有:
match 方法:从起始位置开始查找,一次匹配search 方法:从任何位置开始查找,一次匹配findall 方法:全部匹配,返回列表finditer 方法:全部匹配,返回迭代器split 方法:分割字符串,返回列表sub 方法:替换match 方法用于查找字符串的头部(也可以指定起始位置),它是一次匹配,只要找到了一个匹配的结果就返回,而不是查找所有匹配的结果。它的一般使用形式如下:
match(string[, pos[, endpos]])其中,string 是待匹配的字符串,pos 和 endpos 是可选参数,指定字符串的起始和终点位置,默认值分别是 0 和 len (字符串长度)。因此,当你不指定 pos 和 endpos 时,match 方法默认匹配字符串的头部。
当匹配成功时,返回一个 Match 对象,如果没有匹配上,则返回 None。
>>> import re >>> pattern = re.compile(r'\d+') # 用于匹配至少一个数字 >>> m = pattern.match('one12twothree34four') # 查找头部,没有匹配 >>> print(m) None >>> m = pattern.match('one12twothree34four', 2, 10) # 从'e'的位置开始匹配,没有匹配 >>> print(m) None >>> m = pattern.match('one12twothree34four', 3, 10) # 从'1'的位置开始匹配,正好匹配 >>> print(m # 返回一个 Match 对象) <_sre.SRE_Match object at 0x10a42aac0> >>> m.group(0) # 可省略 0 '12' >>> m.start(0) # 可省略 0 3 >>> m.end(0) # 可省略 0 5 >>> m.span(0) # 可省略 0 (3, 5)在上面,当匹配成功时返回一个 Match 对象,其中:
group([group1, …]) 方法用于获得一个或多个分组匹配的字符串,当要获得整个匹配的子串时,可直接使用 group() 或 group(0);start([group]) 方法用于获取分组匹配的子串在整个字符串中的起始位置(子串第一个字符的索引),参数默认值为 0;end([group]) 方法用于获取分组匹配的子串在整个字符串中的结束位置(子串最后一个字符的索引+1),参数默认值为 0;span([group]) 方法返回 (start(group), end(group))。再看看一个例子:
>>> import re >>> pattern = re.compile(r'([a-z]+) ([a-z]+)', re.I) # re.I 表示忽略大小写 >>> m = pattern.match('Hello World Wide Web') >>> print(m # 匹配成功,返回一个 Match 对象) <_sre.SRE_Match object at 0x10bea83e8> >>> m.group(0) # 返回匹配成功的整个子串 'Hello World' >>> m.span(0) # 返回匹配成功的整个子串的索引 (0, 11) >>> m.group(1) # 返回第一个分组匹配成功的子串 'Hello' >>> m.span(1) # 返回第一个分组匹配成功的子串的索引 (0, 5) >>> m.group(2) # 返回第二个分组匹配成功的子串 'World' >>> m.span(2) # 返回第二个分组匹配成功的子串 (6, 11) >>> m.groups() # 等价于 (m.group(1), m.group(2), ...) ('Hello', 'World') >>> m.group(3) # 不存在第三个分组 Traceback (most recent call last): File "<stdin>", line 1, in <module> IndexError: no such groupsearch 方法用于查找字符串的任何位置,它也是一次匹配,只要找到了一个匹配的结果就返回,而不是查找所有匹配的结果,它的一般使用形式如下:
search(string[, pos[, endpos]])其中,string 是待匹配的字符串,pos 和 endpos 是可选参数,指定字符串的起始和终点位置,默认值分别是 0 和 len (字符串长度)。
当匹配成功时,返回一个 Match 对象,如果没有匹配上,则返回 None。
让我们看看例子:
>>> import re >>> pattern = re.compile('\d+') >>> m = pattern.search('one12twothree34four') # 这里如果使用 match 方法则不匹配 >>> m <_sre.SRE_Match object at 0x10cc03ac0> >>> m.group() '12' >>> m = pattern.search('one12twothree34four', 10, 30) # 指定字符串区间 >>> m <_sre.SRE_Match object at 0x10cc03b28> >>> m.group() '34' >>> m.span() (13, 15)——————————————————————————————————————————————————
现在拥有了正则表达式这把神兵利器,我们就可以进行对爬取到的全部网页源代码进行筛选了。
下面我们一起尝试一下爬取内涵段子网站: http://www.neihan8.com/article/list_5_1.html
打开之后,不难看到里面一个一个灰常有内涵的段子,当你进行翻页的时候,注意url地址的变化:
第一页url: http: //www.neihan8.com/article/list_5_1 .html第二页url: http: //www.neihan8.com/article/list_5_2 .html第三页url: http: //www.neihan8.com/article/list_5_3 .html第四页url: http: //www.neihan8.com/article/list_5_4 .html这样我们的url规律找到了,要想爬取所有的段子,只需要修改一个参数即可。 下面我们就开始一步一步将所有的段子爬取下来吧。
# -*- coding:utf-8 -*- import requests class NeihanSpider(object): def __init__(self): # 请求报头 self.headers = {"User-Agent" : "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko"} # 固定的url地址 self.base_url = "https://www.neihan-8.com/article/list_5_" self.page = 1 def send_request(self, url): """ 接收url地址,发送请求并返回响应 """ response = requests.get(url, headers=self.headers) return response def parse_response(self, response): """ 接收响应,打印响应内容 """ html = response.content print(html) def save_file(self, content_list): pass def main(self): full_url = self.base_url + str(self.page) + ".html" print(full_url) try: response = self.send_request(full_url) self.parse_response(response) except Exception as e: print("[ERROR]: 页面抓取失败 {}".format(full_url)) print(e) print("谢谢使用!") if __name__ == '__main__': spider = NeihanSpider() spider.main()程序正常执行的话,我们会在屏幕上打印了内涵段子第一页的全部html代码。 但是我们发现,html中的中文部分显示的可能是乱码
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E54AO1fG-1572593860657)(assets/luanma-1559639278886.png)]
那么我们需要简单的将得到的网页源代码处理一下:
# -*- coding:utf-8 -*- import requests import re class NeihanSpider(object): def __init__(self): # 请求报头 self.headers = {"User-Agent" : "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko"} # 固定的url地址 self.base_url = "https://www.neihan-8.com/article/list_5_" self.page = 1 def send_request(self, url): """ 接收url地址,发送请求并返回响应 """ response = requests.get(url, headers=self.headers) return response def parse_response(self, response): """ 接收响应,并提取数据 """ # 将网页字符串编码从 gbk 转为 utf-8 html = response.content.decode("gbk").encode("utf-8") print(html) def save_file(self, content_list): pass def main(self): full_url = self.base_url + str(self.page) + ".html" print(full_url) try: response = self.send_request(full_url) self.parse_response(response) except Exception as e: print("[ERROR]: 页面抓取失败 {}".format(full_url)) print(e) print("谢谢使用!") if __name__ == '__main__': spider = NeihanSpider() spider.main()注意 :对于每个网站对中文的编码各自不同,所以html.decode(‘gbk’)的写法并不是通用写法,根据网站的编码而异
这样我们再次执行以下duanzi_spider.py ,会发现之前的中文乱码可以正常显示了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z4WwYpuA-1572593860657)(assets/jiejueluanma-1559640221003.png)]
接下来我们已经得到了整个页面的数据。 但是,很多内容我们并不关心,所以下一步我们需要进行筛选。 如何筛选,就用到了上一节讲述的正则表达式。
首先 import re 然后, 在我们得到的html中进行筛选匹配。我们需要一个匹配规则:
我们可以打开内涵段子的网页,鼠标点击右键 “ 查看源代码 ” 你会惊奇的发现,我们需要的每个段子的内容都是在一个 <div>标签中,而且每个div都有一个属性class = "f18 mb20"
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EzAc3W9N-1572593860657)(assets/neihan-1559637074289.png)]
所以,我们只需要匹配到网页中所有<div class="f18 mb20"> 到 </div> 的数据就可以了。
根据正则表达式,我们可以推算出一个公式是:
<div.*?class="f18 mb20">(.*?)</div>
这个表达式实际上就是匹配到所有div中class="f18 mb20 里面的内容(具体可以看前面正则介绍)然后将这个正则应用到代码中,我们会得到以下代码: import requests import re class NeihanSpider(object): def __init__(self): # 请求报头 self.headers = {"User-Agent" : "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko"} # 固定的url地址 self.base_url = "https://www.neihan-8.com/article/list_5_" # 需要自增的页码值 self.page = 1 # 匹配网页中所有的段子内容,并保存 # re.S 作用是启用DOTALL模式,让 . 也可以匹配换行符 self.pattern_page = re.compile(r'<div class="f18 mb20">(.*?)</div>', re.S) def send_request(self, url): """ 接收url地址,发送请求并返回响应 """ response = requests.get(url, headers=self.headers) return response def parse_response(self, response): """ 接收响应,并提取数据 """ # 将网页字符串编码从 gbk 转为 utf-8 html = response.content.decode("gbk").encode("utf-8") # findall 返回所有符合匹配结果的列表 content_list = self.pattern_page.findall(html) print(content_list) def save_file(self): pass def main(self): full_url = self.base_url + str(self.page) + ".html" print(full_url) try: response = self.send_request(full_url) self.parse_response(response) except Exception as e: print("[ERROR]: 页面抓取失败 {}".format(full_url)) print(e) print("谢谢使用!") if __name__ == '__main__': spider = NeihanSpider() spider.main() 这里需要注意一个是re.S是正则表达式中匹配的一个参数。如果 没有re.S 则是 只匹配一行 有没有符合规则的字符串,如果没有则下一行重新匹配。如果 加上re.S 则是将 所有的字符串 将一个整体进行匹配,findall 将所有匹配到的结果封装到一个list中。 你会发现段子中有很多 <p> , </p> 很是不舒服,实际上这个是html的一种段落的标签。在浏览器上看不出来,但是如果按照文本打印会有<p>出现,那么我们只需要把我们不希望的内容去掉即可了。我们可以如下简单修改一下 . import requests import re class NeihanSpider(object): def __init__(self): # 请求报头 self.headers = {"User-Agent" : "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko"} # 固定的url地址 self.base_url = "https://www.neihan8.com/article/list_5_" # 需要自增的页码值 self.page = 1 # 匹配网页中所有的段子内容,并保存 # re.S 作用是启用DOTALL模式,让 . 也可以匹配换行符 self.pattern_page = re.compile(r'<div class="f18 mb20">(.*?)</div>', re.S) # 匹配每条段子里无用字符部分,并替换为空: # \s 表示空白符,如果空格、换行符等 # &.*?; 表示 HTML实体字符,如 等 # <.*?> 表示 标签,如 <p> 、 <\br> 等 # 或者 u"\u3000".encode("utf-8") 表示 中文全角空格 #self.pattern_content = re.compile(r"\s|&.*?;|<.*?>| ") self.pattern_content = re.compile(r"\s|&.*?;|<.*?>|" + u"\u3000".encode("utf-8")) def send_request(self, url): """ 接收url地址,发送请求并返回响应 """ response = requests.get(url, headers=self.headers) return response def parse_response(self, response): """ 接收响应,并提取数据 """ # 将网页字符串编码从 gbk 转为 utf-8 html = response.content.decode("gbk").encode("utf-8") # findall 返回所有符合匹配结果的列表 content_list = self.pattern_page.findall(html) return content_list def save_file(self): pass def main(self): full_url = self.base_url + str(self.page) + ".html" print(full_url) try: response = self.send_request(full_url) self.parse_response(response) except Exception as e: print("[ERROR]: 页面抓取失败 {}".format(full_url)) print(e) print("谢谢使用!") if __name__ == '__main__': spider = NeihanSpider() spider.main()以上便是一个非常精简使用的小爬虫程序,使用起来很是方便,如果想要爬取其他网站的信息,只需要修改其中某些参数和一些细节就行了。
——————————————————————————————————————————
有同学说,我正则用的不好,处理HTML文档很累,有没有其他的方法?
有!那就是XPath,我们可以先将 HTML文件 转换成 XML文档,然后用 XPath语法 查找 HTML 节点或元素。
W3School官方文档:http://www.w3school.com.cn/xml/index.asp
HTML DOM 定义了访问和操作 HTML 文档的标准方法,以树结构方式表达 HTML 文档。
每个元素以及属性都有一个父。
下面是一个简单的XML例子中,book 元素是 title、author、year 以及 price 元素的父:
<?xml version="1.0" encoding="utf-8"?> <book> <title>Harry Potter</title> <author>J K. Rowling</author> <year>2005</year> <price>29.99</price> </book>元素节点可有零个、一个或多个子。
在下面的例子中,title、author、year 以及 price 元素都是 book 元素的子:
<?xml version="1.0" encoding="utf-8"?> <book> <title>Harry Potter</title> <author>J K. Rowling</author> <year>2005</year> <price>29.99</price> </book>拥有相同的父的节点
在下面的例子中,title、author、year 以及 price 元素都是同胞:
<?xml version="1.0" encoding="utf-8"?> <book> <title>Harry Potter</title> <author>J K. Rowling</author> <year>2005</year> <price>29.99</price> </book>某节点的父、父的父,等等。
在下面的例子中,title 元素的先辈是 book 元素和 bookstore 元素:
<?xml version="1.0" encoding="utf-8"?> <bookstore> <book> <title>Harry Potter</title> <author>J K. Rowling</author> <year>2005</year> <price>29.99</price> </book> </bookstore>某个节点的子,子的子,等等。
在下面的例子中,bookstore 的后代是 book、title、author、year 以及 price 元素:
<?xml version="1.0" encoding="utf-8"?> <bookstore> <book> <title>Harry Potter</title> <author>J K. Rowling</author> <year>2005</year> <price>29.99</price> </book> </bookstore>XPath (XML Path Language) 是一门在 XML 文档中查找信息的语言,可用来在 XML 文档中对元素和属性进行遍历。
W3School官方文档:http://www.w3school.com.cn/xpath/index.asp
XPath 使用路径表达式来选取 XML 文档中的节点或者节点集。这些路径表达式和我们在常规的电脑文件系统中看到的表达式非常相似。
下面列出了最常用的路径表达式:
表达式描述nodename选取此节点的所有子节点。/从根节点选取。//从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。.选取当前节点。…选取当前节点的父节点。@选取属性。在下面的表格中,我们已列出了一些路径表达式以及表达式的结果:
路径表达式结果bookstore选取 bookstore 元素的所有子节点。/bookstore选取根元素 bookstore。注释:假如路径起始于正斜杠( / ),则此路径始终代表到某元素的绝对路径!bookstore/book选取属于 bookstore 的子元素的所有 book 元素。//book选取所有 book 子元素,而不管它们在文档中的位置。bookstore//book选择属于 bookstore 元素的后代的所有 book 元素,而不管它们位于 bookstore 之下的什么位置。//@lang选取名为 lang 的所有属性。谓语用来查找某个特定的节点或者包含某个指定的值的节点,被嵌在方括号中。
在下面的表格中,我们列出了带有谓语的一些路径表达式,以及表达式的结果:
路径表达式结果/bookstore/book[1]选取属于 bookstore 子元素的第一个 book 元素。/bookstore/book[last()]选取属于 bookstore 子元素的最后一个 book 元素。/bookstore/book[last()-1]选取属于 bookstore 子元素的倒数第二个 book 元素。/bookstore/book[position()❤️]选取最前面的两个属于 bookstore 元素的子元素的 book 元素。//title[@lang]选取所有拥有名为 lang 的属性的 title 元素。//title[@lang=’eng’]选取所有 title 元素,且这些元素拥有值为 eng 的 lang 属性。/bookstore/book[price>35.00]选取 bookstore 元素的所有 book 元素,且其中的 price 元素的值须大于 35.00。/bookstore/book[price>35.00]/title选取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值须大于 35.00。XPath 通配符可用来选取未知的 XML 元素。
通配符描述*匹配任何元素节点。@*匹配任何属性节点。node()匹配任何类型的节点。在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:
路径表达式结果/bookstore/*选取 bookstore 元素的所有子元素。//*选取文档中的所有元素。//title[@*]选取所有带有属性的 title 元素。通过在路径表达式中使用“|”运算符,您可以选取若干个路径。
实例
在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:
路径表达式结果//book/title | //book/price选取 book 元素的所有 title 和 price 元素。//title | //price选取文档中的所有 title 和 price 元素。/bookstore/book/title | //price选取属于 bookstore 元素的 book 元素的所有 title 元素,以及文档中所有的 price 元素。下面列出了可用在 XPath 表达式中的运算符:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5FwwDHEA-1572593860658)(assets/xpath01.png)]
这些就是XPath的语法内容,在运用到Python抓取时要先转换为xml。
lxml 是 一个HTML/XML的解析器,主要的功能是如何解析和提取 HTML/XML 数据。
lxml和正则一样,也是用 C 实现的,是一款高性能的 Python HTML/XML 解析器,我们可以利用之前学习的XPath语法,来快速的定位特定元素以及节点信息。
lxml python 官方文档:http://lxml.de/index.html
需要安装C语言库,可使用 pip 安装:pip install lxml (或通过wheel方式安装)
我们利用它来解析 HTML 代码,简单示例:
# lxml_test.py # 使用 lxml 的 etree 库 from lxml import etree text = ''' <div> <ul> <li class="item-0"><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> ''' #利用etree.HTML,将字符串解析为HTML文档 html = etree.HTML(text) # 按字符串序列化HTML文档 result = etree.tostring(html) print(result)输出结果:
<html><body> <div> <ul> <li class="item-0"><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 可以自动修正 html 代码,例子里不仅补全了 li 标签,还添加了 body,html 标签。
除了直接读取字符串,lxml还支持从文件里读取内容。我们新建一个hello.html文件:
<!-- hello.html --> <div> <ul> <li class="item-0"><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"><span class="bold">third item</span></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>再利用 etree.parse() 方法来读取文件。
# lxml_parse.py from lxml import etree # 读取外部文件 hello.html html = etree.parse('./hello.html') result = etree.tostring(html, pretty_print=True) print(result)输出结果与之前相同:
<html><body> <div> <ul> <li class="item-0"><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>1. 获取所有的 <li> 标签
# xpath_li.py from lxml import etree html = etree.parse('hello.html') print(type(html) # 显示etree.parse() 返回类型) result = html.xpath('//li') print(result # 打印<li>标签的元素集合) print(len(result)) print(type(result)) print(type(result[0]))输出结果:
<type 'lxml.etree._ElementTree'> [<Element li at 0x1014e0e18>, <Element li at 0x1014e0ef0>, <Element li at 0x1014e0f38>, <Element li at 0x1014e0f80>, <Element li at 0x1014e0fc8>] 5 <type 'list'> <type 'lxml.etree._Element'>2. 继续获取<li> 标签的所有 class属性
# xpath_li.py from lxml import etree html = etree.parse('hello.html') result = html.xpath('//li/@class') print(result)运行结果
['item-0', 'item-1', 'item-inactive', 'item-1', 'item-0']3. 继续获取<li>标签下hre 为 link1.html 的 <a> 标签
# xpath_li.py from lxml import etree html = etree.parse('hello.html') result = html.xpath('//li/a[@href="link1.html"]') print(result)运行结果
[<Element a at 0x10ffaae18>]4. 获取<li> 标签下的所有 <span> 标签
# xpath_li.py from lxml import etree html = etree.parse('hello.html') #result = html.xpath('//li/span') #注意这么写是不对的: #因为 / 是用来获取子元素的,而 <span> 并不是 <li> 的子元素,所以,要用双斜杠 result = html.xpath('//li//span') print(result)运行结果
[<Element span at 0x10d698e18>]5. 获取 <li> 标签下的<a>标签里的所有 class
# xpath_li.py from lxml import etree html = etree.parse('hello.html') result = html.xpath('//li/a//@class') print(result)运行结果
['blod']6. 获取最后一个 <li> 的 <a> 的 href
# xpath_li.py from lxml import etree html = etree.parse('hello.html') result = html.xpath('//li[last()]/a/@href') # 谓语 [last()] 可以找到最后一个元素 print(result)运行结果
['link5.html']7. 获取倒数第二个元素的内容
# xpath_li.py from lxml import etree html = etree.parse('hello.html') result = html.xpath('//li[last()-1]/a') # text 方法可以获取元素内容 print(result[0].text)运行结果
fourth item8. 获取 class 值为 bold 的标签名
# xpath_li.py from lxml import etree html = etree.parse('hello.html') result = html.xpath('//*[@class="bold"]') # tag方法可以获取标签名 print(result[0].tag)运行结果
span——————————————————————————————————————————————————
现在我们用XPath来做一个简单的爬虫,我们尝试爬取某个贴吧里的所有帖子,并且将该这个帖子里每个楼层发布的图片下载到本地。
#coding:utf-8 import requests from lxml import etree class TiebaSpider(object): def __init__(self): self.headers = {"User-Agent" : "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko"} self.base_url = "http://tieba.baidu.com" self.tieba_name = raw_input("请输入需要抓取的贴吧:") self.begin_page = int(raw_input("请输入需要抓取起始页:")) self.end_page = int(raw_input("请输入需要抓取结束页:")) def send_request(self, url, params={}): """ 发送请求,返回响应 """ response = requests.get(url, params=params, headers=self.headers) return response def parse_page(self, response): """ 解析帖子列表页,获取每个帖子的链接 """ html_obj = etree.HTML(response.content) # 提取帖子详情页链接 link_list = html_obj.xpath("//a[@class='j_th_tit ']/@href") #print(link_list) for link in link_list: url = self.base_url + link # 发送帖子的请求 page_response = self.send_request(url) self.parse_image(page_response) def parse_image(self, response): """ 解析帖子,获取每个图片的链接 """ html_obj = etree.HTML(response.content) # 提取帖子里每个图片的链接 link_list = html_obj.xpath("//img[@class='BDE_Image']/@src") for link in link_list: # 发送图片请求返回响应 image_response = self.send_request(link) # 在保存图片内容 self.save_image(image_response, link[-15:]) def save_image(self, response, filename): """ 接收图片响应,并保存图片 """ print("[INFO]: 正在保存图片 {}..".format(filename)) with open(filename, "wb") as f: f.write(response.content) def main(self): for page in range(self.begin_page, self.end_page + 1): pn = (page - 1) * 50 query_dict = {"kw" : self.tieba_name, "pn" : pn} full_url = self.base_url + "/f?" response = self.send_request(full_url, query_dict) self.parse_page(response) if __name__ == '__main__': spider = TiebaSpider() spider.main() 练习: 爬取果壳网的标题链接,并抓取里面的简介内容,如果该标题有简介则打印。 参考见:goukr.py——————————————————————————————————————————————————
和 lxml 一样,Beautiful Soup 也是一个HTML/XML的解析器,主要的功能也是如何解析和提取 HTML/XML 数据。
lxml 只会局部遍历,而Beautiful Soup 是基于HTML DOM的,会载入整个文档,解析整个DOM树,因此时间和内存开销都会大很多,所以性能要低于lxml。
BeautifulSoup 用来解析 HTML 比较简单,API非常人性化,支持CSS选择器、Python标准库中的HTML解析器,也支持 lxml 的 XML解析器。
Beautiful Soup 3 目前已经停止开发,推荐现在的项目使用Beautiful Soup 4。使用 pip 安装即可:pip install beautifulsoup4
官方文档:http://beautifulsoup.readthedocs.io/zh_CN/v4.4.0
抓取工具速度使用难度安装难度正则最快困难无(内置)BeautifulSoup慢最简单简单lxml快简单一般首先必须要导入 bs4 库
# beautifulsoup4_test.py from bs4 import BeautifulSoup html = """ <html><head><title>The Dormouse's story</title></head> <body> <p class="title" name="dromouse"><b>The Dormouse's story</b></p> <p class="story">Once upon a time there were three little sisters; and their names were <a href="http://example.com/elsie" class="sister" id="link1"><!-- Elsie --></a>, <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>; and they lived at the bottom of a well.</p> <p class="story">...</p> """ #创建 Beautiful Soup 对象 soup = BeautifulSoup(html) #打开本地 HTML 文件的方式来创建对象 #soup = BeautifulSoup(open('index.html')) #格式化输出 soup 对象的内容 print(soup.prettify())运行结果:
<html> <head> <title> The Dormouse's story </title> </head> <body> <p class="title" name="dromouse"> <b> The Dormouse's story </b> </p> <p class="story"> Once upon a time there were three little sisters; and their names were <a class="sister" href="http://example.com/elsie" id="link1"> <!-- Elsie --> </a> , <a class="sister" href="http://example.com/lacie" id="link2"> Lacie </a> and <a class="sister" href="http://example.com/tillie" id="link3"> Tillie </a> ; and they lived at the bottom of a well. </p> <p class="story"> ... </p> </body> </html> 如果我们没有显式地指定解析器,所以默认使用这个系统的最佳可用HTML解析器(“lxml”)。如果你在另一个系统中运行这段代码,或者在不同的虚拟环境中,使用不同的解析器造成行为不同。但是我们可以通过soup = BeautifulSoup(html,“lxml”)方式指定lxml解析器。Beautiful Soup将复杂HTML文档转换成一个复杂的树形结构,每个节点都是Python对象,所有对象可以归纳为4种:
TagNavigableStringBeautifulSoupCommentTag 通俗点讲就是 HTML 中的一个个标签,例如:
<head><title>The Dormouse's story</title></head> <a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a> <p class="title" name="dromouse"><b>The Dormouse's story</b></p>上面的 title head a p等等 HTML 标签加上里面包括的内容就是 Tag,那么试着使用 Beautiful Soup 来获取 Tags:
from bs4 import BeautifulSoup html = """ <html><head><title>The Dormouse's story</title></head> <body> <p class="title" name="dromouse"><b>The Dormouse's story</b></p> <p class="story">Once upon a time there were three little sisters; and their names were <a href="http://example.com/elsie" class="sister" id="link1"><!-- Elsie --></a>, <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>; and they lived at the bottom of a well.</p> <p class="story">...</p> """ #创建 Beautiful Soup 对象 soup = BeautifulSoup(html) print(soup.title) # <title>The Dormouse's story</title> print(soup.head) # <head><title>The Dormouse's story</title></head> print(soup.a) # <a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a> print(soup.p) # <p class="title" name="dromouse"><b>The Dormouse's story</b></p> print(type(soup.p)) # <class 'bs4.element.Tag'>我们可以利用 soup 加标签名轻松地获取这些标签的内容,这些对象的类型是bs4.element.Tag。但是注意,它查找的是在所有内容中的第一个符合要求的标签。如果要查询所有的标签,后面会进行介绍。
对于 Tag,它有两个重要的属性,是 name 和 attrs
print(soup.name) # [document] #soup 对象本身比较特殊,它的 name 即为 [document] print(soup.head.name) # head #对于其他内部标签,输出的值便为标签本身的名称 print(soup.p.attrs) # {'class': ['title'], 'name': 'dromouse'} # 在这里,我们把 p 标签的所有属性打印输出了出来,得到的类型是一个字典。 print(soup.p['class'] # soup.p.get('class')) # ['title'] #还可以利用get方法,传入属性的名称,二者是等价的 soup.p['class'] = "newClass" print(soup.p # 可以对这些属性和内容等等进行修改) # <p class="newClass" name="dromouse"><b>The Dormouse's story</b></p> del soup.p['class'] # 还可以对这个属性进行删除 print(soup.p) # <p name="dromouse"><b>The Dormouse's story</b></p>既然我们已经得到了标签的内容,那么问题来了,我们要想获取标签内部的文字怎么办呢?很简单,用 .string 即可,例如
print(soup.p.string) # The Dormouse's story print(type(soup.p.string)) # In [13]: <class 'bs4.element.NavigableString'>BeautifulSoup 对象表示的是一个文档的内容。大部分时候,可以把它当作 Tag 对象,是一个特殊的 Tag,我们可以分别获取它的类型,名称,以及属性来感受一下
print(type(soup.name)) # <type 'unicode'> print(soup.name ) # [document] print(soup.attrs # 文档本身的属性为空) # {}Comment 对象是一个特殊类型的 NavigableString 对象,其输出的内容不包括注释符号。
print(soup.a) # <a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a> print(soup.a.string) # Elsie print(type(soup.a.string)) # <class 'bs4.element.Comment'>a 标签里的内容实际上是注释,但是如果我们利用 .string 来输出它的内容时,注释符号已经去掉了。
.content
tag 的 .content 属性可以将tag的子节点以列表的方式输出
print(soup.head.contents ) #[<title>The Dormouse's story</title>]输出方式为列表,我们可以用列表索引来获取它的某一个元素
print(soup.head.contents[0]) #<title>The Dormouse's story</title>.children
它返回的不是一个 list,不过我们可以通过遍历获取所有子节点。
我们打印输出 .children 看一下,可以发现它是一个 list 生成器对象
print(soup.head.children) #<listiterator object at 0x7f71457f5710> for child in soup.body.children: print(child)结果:
<p class="title" name="dromouse"><b>The Dormouse's story</b></p> <p class="story">Once upon a time there were three little sisters; and their names were <a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>; and they lived at the bottom of a well.</p> <p class="story">...</p>.contents 和 .children 属性仅包含tag的直接子节点,.descendants 属性可以对所有tag的子孙节点进行递归循环,和 children类似,我们也需要遍历获取其中的内容。
for child in soup.descendants: print(child)运行结果:
<html><head><title>The Dormouse's story</title></head> <body> <p class="title" name="dromouse"><b>The Dormouse's story</b></p> <p class="story">Once upon a time there were three little sisters; and their names were <a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>; and they lived at the bottom of a well.</p> <p class="story">...</p> </body></html> <head><title>The Dormouse's story</title></head> <title>The Dormouse's story</title> The Dormouse's story <body> <p class="title" name="dromouse"><b>The Dormouse's story</b></p> <p class="story">Once upon a time there were three little sisters; and their names were <a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>; and they lived at the bottom of a well.</p> <p class="story">...</p> </body> <p class="title" name="dromouse"><b>The Dormouse's story</b></p> <b>The Dormouse's story</b> The Dormouse's story <p class="story">Once upon a time there were three little sisters; and their names were <a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>; and they lived at the bottom of a well.</p> Once upon a time there were three little sisters; and their names were <a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a> Elsie , <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> Lacie and <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a> Tillie ; and they lived at the bottom of a well. <p class="story">...</p>如果一个标签里面没有标签了,那么 .string 就会返回标签里面的内容。如果标签里面只有唯一的一个标签了,那么 .string 也会返回最里面的内容。例如:
print(soup.head.string) #The Dormouse's story print(soup.title.string) #The Dormouse's storyname 参数可以查找所有名字为 name 的tag,字符串对象会被自动忽略掉
A.传字符串
最简单的过滤器是字符串.在搜索方法中传入一个字符串参数,Beautiful Soup会查找与字符串完整匹配的内容,下面的例子用于查找文档中所有的<b>标签:
soup.find_all('b') # [<b>The Dormouse's story</b>] print(soup.find_all('a')) #[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]B.传正则表达式
如果传入正则表达式作为参数,Beautiful Soup会通过正则表达式的 match() 来匹配内容.下面例子中找出所有以b开头的标签,这表示<body>和<b>标签都应该被找到
import re for tag in soup.find_all(re.compile("^b")): print(tag.name) # body # bC.传列表
如果传入列表参数,Beautiful Soup会将与列表中任一元素匹配的内容返回.下面代码找到文档中所有<a>标签和<b>标签:
soup.find_all(["a", "b"]) # [<b>The Dormouse's story</b>, # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>, # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]通过 text 参数可以搜索文档中的字符串内容,与 name 参数的可选值一样, text 参数接受 字符串 , 正则表达式 , 列表
soup.find_all(text="Elsie") # [u'Elsie'] soup.find_all(text=["Tillie", "Elsie", "Lacie"]) # [u'Elsie', u'Lacie', u'Tillie'] soup.find_all(text=re.compile("Dormouse")) [u"The Dormouse's story", u"The Dormouse's story"]find的用法与find_all一样,区别在于find返回 第一个符合匹配结果,find_all则返回 所有匹配结果的列表。
这就是另一种与 find_all 方法有异曲同工之妙的查找方法,也是返回所有匹配结果的列表。
写 CSS 时,标签名不加任何修饰,类名前加.,id名前加#在这里我们也可以利用类似的方法来筛选元素,用到的方法是 soup.select(),返回类型是 list组合查找即和写 class 文件时,标签名与类名、id名进行的组合原理是一样的,例如查找 p 标签中,id 等于 link1的内容,二者需要用空格分开
print(soup.select('p #link1')) #[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>]直接子标签查找,则使用 > 分隔
print(soup.select("head > title")) #[<title>The Dormouse's story</title>]查找时还可以加入属性元素,属性需要用中括号括起来,注意属性和标签属于同一节点,所以中间不能加空格,否则会无法匹配到。
print(soup.select('a[class="sister"]')) #[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>] print(soup.select('a[href="http://example.com/elsie"]')) #[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>]同样,属性仍然可以与上述查找方式组合,不在同一节点的空格隔开,同一节点的不加空格
print(soup.select('p a[href="http://example.com/elsie"]')) #[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>]以上的 select 方法返回的结果都是列表形式,可以遍历形式输出,然后用 get_text() 方法来获取它的内容。
soup = BeautifulSoup(html, 'lxml') print(type(soup.select('title'))) print(soup.select('title')[0].get_text()) for title in soup.select('title'): print(title.get_text())——————————————————————————————————————————————————
我们以网易社招页面来做演示:
使用BeautifuSoup4解析器,将招聘网页上的职位名称、职位类别、招聘人数、工作地点、发布时间,以及每个职位详情的点击链接存储出来。
import json import requests from bs4 import BeautifulSoup class WangYiSpider(object): def __init__(self): self.base_url = "https://hr.163.com/position/list.do?positionName=¤tPage=" self.page = 1 # 初始化一个字典列表,保存职位信息 self.item_list = [] self.headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36"} def send_request(self, full_url): response = requests.get(full_url, headers=self.headers) return response def parse_response(self, response): html = response.content.decode("utf-8") soup = BeautifulSoup(html, "lxml") node_list = soup.find_all("tr", attrs={"id":""}) node_list.pop(0) for node in node_list: item = {} # 遍历css选择器列表 for name in node.select("td a"): # 从a标签获取职位内容, 保存到字典中 item["position_name"] = name.get_text() # 部门 item["bumen"] = node.find_all("td")[1].get_text() print(item["bumen"]) # 职位 item["position"] = node.find_all("td")[2].get_text() # 工作类型 item["work_type"] = node.find_all("td")[3].get_text() # 工作地点 item["work_place"] = node.find_all("td")[4].get_text() # 招聘人数 item["counts"] = node.find_all("td")[5].get_text() # 发布时间 item["time"] = node.find_all("td")[6].get_text() # 链接详情 item["link"] = node.find_all("td")[0].a.get("href") # 每次循环获取一个职位信息,并保存在同一个列表中 self.item_list.append(item) def save_data(self): """ 实现数据存储 """ # 写入了Unicode字符串的json数据 # 将Python的列表 转为 Json字符串 json_str = json.dumps(self.item_list) # 写入json字符串数据到文件中 with open("wangyi.json", "w") as f: f.write(json_str) # 写入utf-8字符串的json数据(Python2 需要通过sys模块修改解释器编码即可) # json_str = json.dumps(self.item_list, ensure_ascii=False) # # 写入json字符串数据到文件中 # with open("wangyi.json", "w") as f: # f.write(json_str) def main(self): while True: q = input("q退出") if q == "q" or self.page == 125: break full_url = self.base_url + str(self.page) response = self.send_request(full_url=full_url) self.parse_response(response) self.save_data() self.page += 1 if __name__ == '__main__': spider = WangYiSpider() spider.main() 练习: 根据url:https://www.xinpianchang.com/channel/index/type-/sort-like/duration_type-0/resolution_type-/page-2 抓取新片场列表页的数据: 用户名,作者信息,保存片场名, 视频来源,观看次数,点赞数。并将数据保存在json文件中 # xin_pian_chang_bs4.py[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MNzoTDgk-1572593860660)(assets/1567060774891.png)]
——————————————————————————————————————————
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,它使得人们很容易的进行阅读和编写。同时也方便了机器进行解析和生成。适用于进行数据交互的场景,比如网站前台与后台之间的数据交互。
JSON和XML的比较可谓不相上下。
Python 2.7中自带了JSON模块,直接import json就可以使用了。
官方文档:http://docs.python.org/library/json.html
Json在线解析网站:http://www.json.cn/#
json简单说就是javascript中的对象和数组,所以这两种结构就是对象和数组两种结构,通过这两种结构可以表示各种复杂的结构
对象:对象在js中表示为{ }括起来的内容,数据结构为 { key:value, key:value, ... }的键值对的结构,在面向对象的语言中,key为对象的属性,value为对应的属性值,所以很容易理解,取值方法为 对象.key 获取属性值,这个属性值的类型可以是数字、字符串、数组、对象这几种。数组:数组在js中是中括号[ ]括起来的内容,数据结构为 ["Python", "javascript", "C++", ...],取值方式和所有语言中一样,使用索引获取,字段值的类型可以是 数字、字符串、数组、对象几种。import json
json模块提供了四个功能:dumps、dump、loads、load,用于字符串 和 python数据类型间进行转换。
把Json格式字符串解码转换成Python对象 从json到python的类型转化对照如下:
# json_loads.py import json strList = '[1, 2, 3, 4]' strDict = '{"city": "北京", "name": "小明"}' json.loads(strList) # [1, 2, 3, 4] json.loads(strDict) # json数据自动按Unicode存储 # {u'city': u'\u5317\u4eac', u'name': u'\u5c0f\u660e'}实现python类型转化为json字符串,返回一个str对象 把一个Python对象编码转换成Json字符串
从python原始类型向json类型的转化对照如下:
# json_dumps.py import json import chardet listStr = [1, 2, 3, 4] tupleStr = (1, 2, 3, 4) dictStr = {"city": "北京", "name": "小明"} json.dumps(listStr) # '[1, 2, 3, 4]' json.dumps(tupleStr) # '[1, 2, 3, 4]' # 注意:json.dumps() 处理中文时默认使用的ascii编码,会导致中文无法正常显示 print(json.dumps(dictStr) ) # {"city": "\u5317\u4eac", "name": "\u5c0f\u660e"} # 记住:处理中文时,添加参数 ensure_ascii=False 来禁用ascii编码 print(json.dumps(dictStr, ensure_ascii=False) ) # {"city": "北京", "name": "小明"}将Python内置类型序列化为json对象后写入文件
# json_dump.py import json listStr = [{"city": "北京"}, {"name": "大刘"}] json.dump(listStr, open("listStr.json","w"), ensure_ascii=False) dictStr = {"city": "北京", "name": "大刘"} json.dump(dictStr, open("dictStr.json","w"), ensure_ascii=False)读取文件中json形式的字符串元素 转化成python类型
# json_load.py import json strList = json.load(open("listStr.json")) print(strList) # [{"city": "\u5317\u4eac", "name": "\u5c0f\u660e"}] strDict = json.load(open("dictStr.json")) print(strDict)JsonPath 是一种信息抽取类库,是从JSON文档中抽取指定信息的工具,提供多种语言实现版本,包括:Javascript, Python, PHP 和 Java。
JsonPath 对于 JSON 来说,相当于 XPath 对于 XML。
安装方法:pip install jsonpath
官方文档:http://goessner.net/articles/JsonPath
Json结构清晰,可读性高,复杂度低,非常容易匹配,下表中对应了XPath的用法。
XPathJSONPath描述/$根节点.@现行节点/.or[]取子节点..n/a取父节点,Jsonpath未支持//..就是不管位置,选择所有符合条件的条件**匹配所有元素节点@n/a根据属性访问,Json不支持,因为Json是个Key-value递归结构,不需要属性访问。[][]迭代器标示(可以在里边做简单的迭代操作,如数组下标,根据内容选值等)|[,]支持迭代器中做多选。[]?()支持过滤操作.n/a()支持表达式计算()n/a分组,JsonPath不支持我们以拉勾网城市JSON文件 http://www.lagou.com/lbs/getAllCitySearchLabels.json 为例,获取所有城市。
# jsonpath_lagou.py import urllib2 import jsonpath import json url = 'http://www.lagou.com/lbs/getAllCitySearchLabels.json' request =urllib2.Request(url) response = urllib2.urlopen(request) html = response.read() # 把json格式字符串转换成python对象 jsonobj = json.loads(html) # 从根节点开始,匹配name节点 citylist = jsonpath.jsonpath(jsonobj,'$..name') print(citylist) print(type(citylist)) fp = open('city.json','w') content = json.dumps(citylist, ensure_ascii=False) print(content) fp.write(content.encode('utf-8')) fp.close() 练习1: 创建一个py文件,抓取某一个百度贴吧名的所有列表链接,并保存到json文件中。 练习参考答案见:baidu_save_to_json.py 练习2:https://www.lagou.com 1.输入需要抓取的职位及城市,爬取拉钩所有页面,抓取列表页每个职位的的薪水、城市、职位名称、地区、创建时间、公司规模大小、公司全称、公司编号,保存到json文件中, 2.步骤1的基础上,继续抓取详情页的数据,保存到txt文本。 参考练习答案见:lagouspdier.py注意事项:
json.loads() 是把 Json格式字符串解码转换成Python对象,如果在json.loads的时候出错,要注意被解码的Json字符的编码,如果传入的字符串的编码不是UTF-8的话,需要指定字符编码的参数encoding
如:
dataDict = json.loads(jsonStrGBK);jsonStrGBK是JSON字符串,假设其编码本身是非UTF-8的话而是GBK 的,那么上述代码会导致出错,改为对应的:
dataDict = json.loads(jsonStrGBK, encoding="GBK");这是中国程序员最苦逼的地方,什么乱码之类的几乎都是由汉字引起的。 其实编码问题很好搞定,只要记住一点:
任何平台的任何编码 都能和 Unicode 互相转换
UTF-8 与 GBK 互相转换,那就先把UTF-8转换成Unicode,再从Unicode转换成GBK,反之同理。
# 这是一个 UTF-8 编码的字符串 utf8Str = "你好地球" # 1. 将 UTF-8 编码的字符串 转换成 Unicode 编码 unicodeStr = utf8Str.decode("UTF-8") # 2. 再将 Unicode 编码格式字符串 转换成 GBK 编码 gbkData = unicodeStr.encode("GBK") # 1. 再将 GBK 编码格式字符串 转化成 Unicode unicodeStr = gbkData.decode("gbk") # 2. 再将 Unicode 编码格式字符串转换成 UTF-8 utf8Str = unicodeStr.encode("UTF-8")decode的作用是将其他编码的字符串转换成 Unicode 编码
encode的作用是将 Unicode 编码转换成其他编码的字符串
一句话:UTF-8是对Unicode字符集进行编码的一种编码方式1.什么是CSV?
CSV,全称为Comma-Separated Values,中文可以叫做逗号分隔值或字符分隔值,其文件以纯文本形式存储表格数据。该文件是一个字符序列,可以由任意数目的记录组成,记录间以某种换行符分隔。每条记录由字段组成,字段间的分隔符是其他字符或字符串,最常见的是逗号或者制表符。不过所有记录都有完全相同的字段序列,相当于一个结构化表的纯文本形式。它比Excel文件更为简洁,XLS文本是电子表格,它包含了文本、数值、公式和数据等内容,而CSV中不包含这些内容,就是特定字符分隔的纯文本,结构简单清晰。
2.保存成csv文本格式
import csv def save_csv(): # 打开文件 file = open("1.csv","w", newline="") # 创建csv_writer对象 csv_writer = csv.writer(file) # 写入首行 csv_writer.writerow(["s_id", "s_name", "s_age"]) # 写入数据 csv_writer.writerows([[1, "james", "34"],[2,"kobe","36"]]) file.close() def json_save_to_csv(): # 打开json文件 file_json = open("lagou4.json", "r") # 打开并创建csv文件 file_csv = open("lagou.csv", "w") # 读取json文件内容 content = file_json.read() # 将json数据进行反序列化 data_list = json.loads(content) # 获取表头 sheet_data = data_list[0].keys() # 获取数据内容 content_data = [data.values() for data in data_list] # 创建csv_writer对象 csv_writer = csv.writer(file_csv) # 将表头写入 csv_writer.writerow(sheet_data) # 写入数据内容 csv_writer.writerows(content_data) if __name__ == '__main__': save_csv()练习2:https://www.lagou.com 1.输入需要抓取的职位及城市,爬取拉钩所有页面,抓取列表页每个职位的的薪水、城市、职位名称、地区、创建时间、公司规模大小、公司全称、公司编号,保存到json文件中, 2.步骤1的基础上,继续抓取详情页的数据,保存到txt文本。 参考练习答案见:lagouspdier.py
注意事项: json.loads() 是把 Json格式字符串解码转换成Python对象,如果在json.loads的时候出错,要注意被解码的Json字符的编码,如果传入的字符串的编码不是UTF-8的话,需要指定字符编码的参数`encoding` 如: ```python dataDict = json.loads(jsonStrGBK);jsonStrGBK是JSON字符串,假设其编码本身是非UTF-8的话而是GBK 的,那么上述代码会导致出错,改为对应的:
dataDict = json.loads(jsonStrGBK, encoding="GBK");这是中国程序员最苦逼的地方,什么乱码之类的几乎都是由汉字引起的。 其实编码问题很好搞定,只要记住一点:
任何平台的任何编码 都能和 Unicode 互相转换
UTF-8 与 GBK 互相转换,那就先把UTF-8转换成Unicode,再从Unicode转换成GBK,反之同理。
# 这是一个 UTF-8 编码的字符串 utf8Str = "你好地球" # 1. 将 UTF-8 编码的字符串 转换成 Unicode 编码 unicodeStr = utf8Str.decode("UTF-8") # 2. 再将 Unicode 编码格式字符串 转换成 GBK 编码 gbkData = unicodeStr.encode("GBK") # 1. 再将 GBK 编码格式字符串 转化成 Unicode unicodeStr = gbkData.decode("gbk") # 2. 再将 Unicode 编码格式字符串转换成 UTF-8 utf8Str = unicodeStr.encode("UTF-8")decode的作用是将其他编码的字符串转换成 Unicode 编码
encode的作用是将 Unicode 编码转换成其他编码的字符串
一句话:UTF-8是对Unicode字符集进行编码的一种编码方式1.什么是CSV?
CSV,全称为Comma-Separated Values,中文可以叫做逗号分隔值或字符分隔值,其文件以纯文本形式存储表格数据。该文件是一个字符序列,可以由任意数目的记录组成,记录间以某种换行符分隔。每条记录由字段组成,字段间的分隔符是其他字符或字符串,最常见的是逗号或者制表符。不过所有记录都有完全相同的字段序列,相当于一个结构化表的纯文本形式。它比Excel文件更为简洁,XLS文本是电子表格,它包含了文本、数值、公式和数据等内容,而CSV中不包含这些内容,就是特定字符分隔的纯文本,结构简单清晰。
2.保存成csv文本格式
import csv def save_csv(): # 打开文件 file = open("1.csv","w", newline="") # 创建csv_writer对象 csv_writer = csv.writer(file) # 写入首行 csv_writer.writerow(["s_id", "s_name", "s_age"]) # 写入数据 csv_writer.writerows([[1, "james", "34"],[2,"kobe","36"]]) file.close() def json_save_to_csv(): # 打开json文件 file_json = open("lagou4.json", "r") # 打开并创建csv文件 file_csv = open("lagou.csv", "w") # 读取json文件内容 content = file_json.read() # 将json数据进行反序列化 data_list = json.loads(content) # 获取表头 sheet_data = data_list[0].keys() # 获取数据内容 content_data = [data.values() for data in data_list] # 创建csv_writer对象 csv_writer = csv.writer(file_csv) # 将表头写入 csv_writer.writerow(sheet_data) # 写入数据内容 csv_writer.writerows(content_data) if __name__ == '__main__': save_csv()——————————————————————————————————————————————————