概要
Lxml库
是基于libxml2
的XML解析库
的Python
封装。该模块使用C
语言编写,解析速度比BeautifulSoup
更快。Lxml库
使用Xpath
语法解析定位网页数据。
将讲解Lxml库在Mac
和Linux
环境中的安装方法,还将介绍Lxml库
的使用方法及Xpath
的语法知识,而且通过案例对正则表达式
、BeautifulSoup
和Lxml
进行性能对比。
主要涉及的知识点:
Lxml库:学会各个系统下Lxml库的安装和使用方法。
Xpath语法:学会Xpath语法并通过Xpath语法提取所需的网页信息。
性能对比:通过案例对
正则表达式
、BeautifulSoup
和Lxml
进行性能对比。Requests和Lxml库组合应用:演示如何利用这两大库进行爬虫的方法和技巧。
1. Lxml库的安装与使用方法
Lxml库解析网页数据快,但安装过程却相对困难。主要讲解Lxml库在Mac
和Linux
环境中的安装方法及Lxml库的简单用法。
1.1 Lxml库的安装(Mac、Linux)
这里我们主要讲下Linux系统下的安装
- Linux系统
Linux系统安装Lxml库最简单,在终端输入:
apt-get install Python3-lxml
这样就完后才能了Linux系统下Lxml库的安装。
1.2 Lxml库的使用
- 修正HTML代码
Lxml为XML解析库,但也很好的支持了HTML文档的解析功能,这为使用Lxml库爬取网络信息提供了支持条件。
这样就可以通过Lxml库来解析HTML文档了:
from lxml import etree
text = '''
<div>
<ul>
<li class ="red"><h1>red flowers</h1></li>
<li class ="yellow"><h2>yellow flowers</h2></li>
<li class ="white"><h3>white flowers</h3></li>
<li class ="black"><h4>black flowers</h4></li>
<li class ="blue"><h5>blue flowers</h5></li>
</ul>
</div>
'''
html = etree.HTML(text)
print(html)
# Lxml库解析数据,为Element对象
打印结果如下:
首先导入Lxml中的etree
库,然后利用etree.HTML
进行初始化,最后把结果打印出来。可以看出,etree
库把HTML
文档解析为Element
对象,可以通过以下代码输出解析过的HTML
文档。
from lxml import etree
text = '''
<div>
<ul>
<li class ="red"><h1>red flowers</h1></li>
<li class ="yellow"><h2>yellow flowers</h2></li>
<li class ="white"><h3>white flowers</h3></li>
<li class ="black"><h4>black flowers</h4></li>
<li class ="blue"><h5>blue flowers</h5>
</ul>
</div>
'''
html = etree.HTML(text)
result = etree.tostring(html)
print(result)
# Lxml库解析可自动修正HTML
打印结果如下:
这里体现了Lxml
库一个非常使用的功能就是自动修正HTML
代码,应该注意到了最后一个li
标签,其实是把尾标签删掉了,是不闭合的。不过Lxml
因为集成了libxml2
的特性,具有自动修正HTML
代码的功能,这里不仅补齐了li
标签,而且还添加了html
和body
标签。
- 读取HTML文件
除了直接读取字符串,Lxml库
还支持从文件
中提取内容。我们可以通过Pycharm
新建一个flower.html
文件。在所需建立文件的位置右击,在弹出的快捷菜单中选择New|HTML File
命令,如下图:
新建好的HTML文件,已经自动生成了html、head和body标签,也可以通过单击Pycharm右上角的浏览器符号,在本地打开制作好的HTML文件,如下图:
把前面的字符串复制在HTML文档中,如下图,最后通过浏览器打开制作好的HTML文件。
这样便可通过Lxml库读取HTML文件中的内容了,可以通过下面的代码读取:
from lxml import etree
html = etree.parse('flower.html')
result = etree.tostring(html, pretty_print=True)
print(result)
注:
<meta charset="UTF-8"/>
中的/
必须要带,不然会解析报错。
- 解析HTML文件
完成了前面的步骤后,便可利用requests
库来获取HTML
文件,用Lxml
库来解析HTML
文件了。
import requests
from lxml import etree
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) '
'AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/70.0.3538.67 Safari/537.36'
}
res = requests.get('https://book.douban.com/top250', headers=headers)
html = etree.HTML(res.text)
result = etree.tostring(html)
print(result)
2. Xpath 语法
Xpath
是一门在XML
文档中查找信息的语言,对HTML
文档也有很好的支持。将介绍Xpath
的常用语法,Xpath
语言在爬虫中的使用技巧。最后通过案例对正则表达式、BeautifulSoup和Lxml进行性能对比。
2.1 节点关系
- 父节点
每个元素及属性都有一个父节点,在下面的例子中,user
元素是name
、sex
、id
及goal
元素的父节点。
<user>
<name>xiao ming</name>
<sex>man</sex>
<id>34</id>
<goal>89</goal>
</user>
- 子节点
元素节点可有0个、一个或多个子节点,在下面的例子中,name
、sex
、id
及goal
元素都是user元素的子节点。
<user>
<name>xiao ming</name>
<sex>man</sex>
<id>34</id>
<goal>89</goal>
</user>
- 同胞节点
同胞节点拥有相同的父节点,在下面的例子中,name
、sex
、id
及goal
元素都是同胞节点
<user>
<name>xiao ming</name>
<sex>man</sex>
<id>34</id>
<goal>89</goal>
</user>
- 先辈节点
先辈节点指某节点的父、父的父节点等,在下面的例子中,name元素的先辈是user元素和user_database元素:
<user_database>
<user>
<name>xiao ming</name>
<sex>man</sex>
<id>34</id>
<goal>89</goal>
</user>
</user_database>
- 后代节点
后代节点指某个节点的子节点、子节点的子节点等,在下面的例子中,user_database的后代是user、name、sex、id及goal元素:
<user_database>
<user>
<name>xiao ming</name>
<sex>man</sex>
<id>34</id>
<goal>89</goal>
</user>
</user_database>
2.2 节点选择
Xpath使用路径表达式在XML文档中选取节点。节点是通过演着路径或者step来选取的,如下表:
表达式 | 描述 |
---|---|
nodename | 选取此节点的所有子节点 |
/ | 从根节点选取 |
// | 从匹配选择的当前节点选择文档中的节点,而不考虑他们的位置 |
. | 选取当前节点 |
.. | 选取当前节点的父节点 |
@ | 选取属性 |
通过前面的例子进行举例,如下表所示:
表达式 | 描述 |
---|---|
user_database | 选取元素user_database的所有子节点 |
/user_database | 选取根元素user_database。注释:假如路径起始于正斜杠(/),则此路径始终代表到某元素的绝对路径 |
user_database/user | 选取属于user_database的子元素的所有user元素 |
//user | 选取所有user子元素,而不管它们在文档中的位置 |
user_database//user | 选取属于user_database元素的后代的所有user元素,而不管它们位于user_database之下的什么位置 |
//@attribute | 选取名为attribute的所有属性 |
Xpath语法中的谓语用来查找某个特定的节点或者包含某个指定值的节点,谓语被嵌在方括号中。常见的谓语如下表:
路径表达式 | 结果 |
---|---|
/user_database/user[1] | 选取属于user_database子元素的第一个user元素 |
//li[@attribute] | 选取所有拥有名为attribute属性的li元素 |
//li[@attribute='red'] | 选取所有li元素,且这些元素拥有值为red的attribute属性 |
Xpath中也可以使用通配符来选取位置的元素,常用的就是“*”通配符,它可以匹配任何元素节点。
2.3 使用技巧
在爬虫实战中,Xpath路径可以通过Chrome复制得到,如下图:
(1)鼠标光标定位到想要提取的数据位置,右击,从弹出的快捷菜单中选择“检查”命令。
(2)在网页源代码中右击所选元素。
(3)从弹出的快捷菜单中选择Copy Xpath
命令,这时便能得到。
//*[@id="qiushi_tag_121174862"]/div[1]/a[2]/h2
通过代码即可得到用户id:
import requests
from lxml import etree
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) '
'AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/70.0.3538.67 Safari/537.36'
}
url = 'http://www.qiushibaike.com/text/'
res = requests.get(url, headers=headers)
selector = etree.HTML(res.text)
print(selector)
id = selector.xpath('//*[@id="qiushi_tag_121166084"]/div[1]/a[2]/h2/text()')
print(id)
注意:通过/text()可以获取标签中的文字信息。
结果为
['\n谁抢了我微信昵称\n']
上面的结果为列表的数据结构,可以通过切片获取为字符串数据结构:
import requests
from lxml import etree
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) '
'AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/70.0.3538.67 Safari/537.36'
}
url = 'http://www.qiushibaike.com/text/'
res = requests.get(url, headers=headers)
selector = etree.HTML(res.text)
id = selector.xpath('//*[@id="qiushi_tag_121166084"]/div[1]/a[2]/h2/text()')[0]
print(id)
当需要进行用户ID的批量爬取时,通过类似于BeautifulSoup
中的selector()
方法删除谓语部分是不可行的。这时的思路为“先抓大后抓小,寻找循环点”。打开Chrome浏览器进行“检查”,通过“三角形符号”折叠元素,找到每个段子完整的信息标签,如下图所示,每一个div标签为一个段子信息。
(1)首先通过复制构造div标签路径,此时的路径为:
//*[@class="article block untagged mb15" ]
这样就定位到了每个段子信息,这就是循环点。
(2)通过Chrome浏览器进行“检查”定位用户ID,复制Xpath到记事本中。
//*[@id="qiushi_tag_121174765"]/div[1]/a[2]/h2
因为第一部分为循环部分,将其删除得到:
div[1]/a[2]/h2
这便是用户ID的信息。
注意:这里就不需要斜线作为开头了。
同时也可以这样写:
import requests
from lxml import etree
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) '
'AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/70.0.3538.67 Safari/537.36'
}
url = 'https://www.qiushibaike.com/text/'
res = requests.get(url, headers=headers)
# print(res.text)
# 标签为<div class="article block untagged mb15 typs_hot" id="qiushi_tag_121132915">
selector = etree.HTML(res.text)
url_infos = selector.xpath('//div[@class="article block untagged mb15 typs_hot"]/div[1]/a[2]/h2/text()')
print(url_infos)
更可以:
url_infos = selector.xpath('//div/div[1]/a[2]/h2/text()')
print(url_infos)
有时候会遇到相同的字符开头的多个标签:
<li class="tag-1">需要的内容1</li>
<li class="tag-2">需要的内容2</li>
<li class="tag-3">需要的内容3</li>
想同时爬取时,不需要构造多个Xpath路径,通过starts-with()
便可以获取多个标签内容。
from lxml import etree
html1 = '''
<li class="tag-1">需要的内容1</li>
<li class="tag-2">需要的内容2</li>
<li class="tag-3">需要的内容3</li>
'''
selector = etree.HTML(html1)
contents = selector.xpath('//li[starts-with(@class,"tag")]/text() ')
print(contents)
for content in contents:
print(content)
# starts-with可获取类似标签的信息
上面的示例也可以用starts-with方法来实现:
import requests
from lxml import etree
url = 'https://www.qiushibaike.com/text/'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) '
'AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/70.0.3538.67 Safari/537.36'
}
response = requests.get(url, headers=headers)
# print(response.text)
selector = etree.HTML(response.text)
# print(selector)
# 循环点: <div class="article block untagged mb15 typs_long" id="qiushi_tag_121169478">
# //*[@id="qiushi_tag_121169478"]/div[1]/a[2]/h2
result = selector.xpath('//div[starts-with(@class,"article block untagged mb15")]/div[1]/a[2]/h2/text()')
print(result)
当遇到标签套标签情况时:
<div class="red">需要的内容1
<h1>需要的内容2</h1>
</div>>
想同时获取文本内容,可以通过string(.)完成:
from lxml import etree
html2 = '''
<div class="red">需要的内容1
<h1>需要的内容2</h1>
</div>>
'''
selector = etree.HTML(html2)
content1 = selector.xpath('//div[@class="red"]')[0]
content2 = content1.xpath('string(.)')
print(content2)
# string(.)方法可用于标签套标签情况
2.4 性能对比
前面提到Lxml库的解析速度快,但是口说无凭,将会通过代码对正则表达式、BeautifulSoup、Lxml进行性能对比。
(1)通过3种方法爬取糗事百科文字内容中的信息,如下图:
(2)由于是比较性能,爬取的信息并不是很多,爬取的信息有:用户ID、发表段子文字信息、好笑数量和评论数量,如下图:
(3)爬取的数据只做返回,不存储。
代码如下:
import requests
from lxml import etree
from bs4 import BeautifulSoup
import re
import time
urls = ['https://www.qiushibaike.com/text/page/{}'.format(str(i)) for i in range(1, 14)]
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) '
'AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/70.0.3538.67 Safari/537.36'
}
def re_scraper(url):
# 用正则表达式
res = requests.get(url, headers=headers)
ids = re.findall('<h2>(.*?)</h2>', res.text, re.S)
# print(len(ids))
contents = re.findall('<div class="content">.*?<span>(.*?)</span>', res.text, re.S)
# print(len(contents))
laughs = re.findall('<span class="stats-vote">.*?<i class="number">(\d+)</i>', res.text, re.S)
# print(len(laughs))
# print(laughs)
# <i class="number">47</i> 评论
comments = re.findall('<i class="number">(\d+)</i> 评论', res.text, re.S)
# print(len(comments))
# print(comments)
for id, content, laugh, comment in zip(ids, contents, laughs, comments):
info = {
'id': id.strip(),
'content': content.strip(),
'laugh': laugh,
'comment': comment
}
return info
def bs_scraper(url):
# BeautifulSoup爬虫
res = requests.get(url, headers=headers)
soup = BeautifulSoup(res.text, 'lxml')
# #qiushi_tag_121175295 > div.author.clearfix > a:nth-child(2) > h2
ids = soup.select('div.author.clearfix > a > h2')
# print(ids)
# print(len(ids))
# #qiushi_tag_121190672 > a > div > span
# 直接用a>div>span 检索出来的结果会有56条,应该缩小检索范围
contents = soup.select(' a.contentHerf > div > span')
# print(contents)
# print(len(contents))
# #qiushi_tag_121190672 > div.stats > span.stats-vote > i
laughs = soup.select('div.stats > span.stats-vote > i')
# print(laughs)
# print(len(laughs))
# #c-121182508 > i
comments = soup.select('span > a.qiushi_comments > i')
# print(comments)
# print(len(comments))
for id, content, laugh, comment in zip(ids, contents, laughs, comments):
info = {
'id': id.get_text(),
'content': content.getText(),
'laugh': laugh.getText(),
'comment': comment.get_text()
}
return info
def lxml_scraper(url):
# lxml爬虫
res = requests.get(url, headers=headers)
selector = etree.HTML(res.text)
# <div class="article block untagged mb15 typs_long" id="qiushi_tag_121178203">
# //*[@id="qiushi_tag_121178203"]
url_infos = selector.xpath('//div[starts-with(@id,"qiushi_tag_")]')
# print(url_infos)
# print(len(url_infos))
try:
for url_info in url_infos:
# //*[@id="qiushi_tag_121178203"]/div[1]/a[2]/h2
# //*[@id="qiushi_tag_121192750"]/div[1]/span[2]/h2
id = url_info.xpath('div[1]/a[2]/h2/text()')
# print(id)
# //*[@id="qiushi_tag_121191682"]/a[1]/div/span
content = url_info.xpath('a[1]/div/span/text()')
# print(content)
# //*[@id="qiushi_tag_115909114"]/div[2]/span[1]/i
laugh = url_info.xpath('div[2]/span[1]/i/text()')
# print(laugh)
# //*[@id="c-121164484"]/i
# //*[@id="c-121164484"]
# //*[@id="qiushi_tag_121164484"]/div[2]/span[2]
comment = url_info.xpath('div[2]/span[2]/a[1]/i/text()')
# print(comment)
info = {
'id': id,
'content': content,
'laugh': laugh,
'comment': comment
}
return info
except IndexError:
print("error")
# print(re_scraper(urls[0]))
# print(bs_scraper(urls[0]))
# print((lxml_scraper(urls[0])))
if __name__ == '__main__':
# 程序主入口
for name, scraper in [('Regular 67 expressions', re_scraper), ('BeautifulSoup', bs_scraper),
('Lxml', lxml_scraper)]:
start = time.time()
for url in urls:
scraper(url)
end = time.time()
print(name, end - start)
由于硬件条件的不同,执行的结果会存在一定的差异性。下表总结了各种爬虫方法的优缺点:
爬取方法 | 性能 | 使用难度 | 安装难度 |
---|---|---|---|
正则表达式 | 快 | 困难 | 简单(内置模块) |
BeautifulSoup | 慢 | 剪短 | 简单 |
Lxml | 快 | 简单 | 相对困难 |
当网页结构简单并且想要避免额外依赖的话(不需要安装库),使用正则表达式更为合适。当需要爬取的数据量较少时,使用较慢的BeautifulSoup也不成问题。当数据量大,需要追求效益时,Lxml是最好的选择。
3. 综合案例1----爬取豆瓣网图书TOP250的数据
将利用Requests和Lxml第三方库,爬取豆瓣网图书TOP250的数据,并存储到CSV格式的文件中。
3.1 将数据存储到CSV文件中
前面爬取的数据要么打印到屏幕上,要么存储到TXT文档中,这些格式并不利于数据的存储。那么大家平时是用什么来存储数据的呢?大部分读者可能是使用微软公司的Excel来储存数据的,大规模的数据则是使用数据库。CSV是存储表格数据的常用文件格式,Excel和很多应用都支持CSV格式,因为它很简洁。下面就是一个CSV文件的例子:
id,name
1,xiaoming
2,zhangsan
3,peter
Python中的csv
库可以创建CSV文件,并写入数据:
import csv
# 创建CSV文件
fp = open('C:/Users/Think/Desktop/test.csv', 'w+')
writer = csv.writer(fp)
writer.writerow(('id', 'name'))
writer.writerow(('1', 'xiaoming'))
writer.writerow(('2', 'zhangsan'))
writer.writerow(('3', 'peter'))
# 写入行
这时的本机桌面上会生成名为test的CSV文件,用记事本打开,效果如下:
3.2 爬虫思路分析
(1) 爬取的内容为豆瓣网图书TOP250的信息。
(2)爬取豆瓣网图书TOP250的10页信息,通过手动浏览,以下为前4页的网址:
https://book.douban.com/top250?start=0
https://book.douban.com/top250?start=25
https://book.douban.com/top250?start=50
https://book.douban.com/top250?start=75
(3)需要爬取的信息有:书名、书本的URL链接、作者、出版社和出版时间,书本价格、评分和评价,如下图:
注意:这里只爬了第一作者
(4)运用Python中的csv库,把爬取的信息存储在本地的CSV文本中。
3.3 爬虫代码及分析
from lxml import etree
import requests
import csv
fp = open('C:/Users/Think/Desktop/doubanbook.csv', 'wt', newline='', encoding='utf-8')
writer = csv.writer(fp)
# 写入header
writer.writerow(('name', 'url', 'author', 'publisher', 'date', 'price', 'rate', 'comment'))
urls = ['https://book.douban.com/top250?start={}'.format(str(i * 25)) for i in range(0, 10)]
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) '
'AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/70.0.3538.67 Safari/537.36'
}
for url in urls:
# print(url)
html = requests.get(url, headers=headers)
selector = etree.HTML(html.text)
# //*[@id="content"]/div/div[1]/div/table[1]/tbody/tr
infos = selector.xpath('//tr[@class="item"]')
# print(len(infos))
# name , url , author , publisher , date , price , rate , comment
for info in infos:
# //*[@id="content"]/div/div[1]/div/table[1]/tbody/tr/td[2]/div[1]/a
# td[2]/div[1]/a
name = info.xpath('td[2]/div[1]/a/text()')[0]
# print(name.strip())
# //*[@id="content"]/div/div[1]/div/table[1]/tbody/tr/td[2]/div[1]/a
url = info.xpath('td[2]/div[1]/a/@href')[0]
# print(url)
# //*[@id="content"]/div/div[1]/div/table[1]/tbody/tr/td[2]/p[1]
book_infos = info.xpath('td[2]/p[1]/text()')[0].split('/')
# print(book_infos)
author = book_infos[0]
# print(author)
publisher = book_infos[-3]
date = book_infos[-2]
price = book_infos[-1]
# //*[@id="content"]/div/div[1]/div/table[1]/tbody/tr/td[2]/div[2]/span[2]
rate = info.xpath('td[2]/div[2]/span[2]/text()')[0]
# print(rate)
# //*[@id="content"]/div/div[1]/div/table[1]/tbody/tr/td[2]/p[2]/span
comments = info.xpath('td[2]/p[2]/span/text()')
# print(comments)
comment = comments[0] if len(comments) != 0 else '空'
writer.writerow((name, url, author, publisher, date, price, rate, comment))
fp.close()
程序运行的结果保存在计算机里文件名为doubanbook的csv文件中,如通过excel打开会出现乱码错误,如图:
可以通过记事本打开,将其另存为编码为UTF-8的文件,便不会出现乱码问题。
这时再通过Excel打开文件,便不会出现乱码问题了。
4. 综合案例2----爬取起点中文网小说信息
将利用Requests 和 Lxml第三方库,爬取起点中文网小说信息,并存储到Excel文件中。
4.1 将数据存储到Excel文件中
使用Python的第三方库xlwt
,可将数据写入Excel中,通过PIP进行安装即可。
通过下面的代码,便可将数据写入Excel中:
import xlwt
# 将数据写入Excel的库文件中
# 创建工作簿
book = xlwt.Workbook(encoding='utf-8')
# 创建工作表
sheet = book.add_sheet('Sheet1')
# 在相应单元格写入数据
sheet.write(0, 0, 'python')
sheet.write(1, 1, 'love')
sheet.write(1, 3, 'ozan')
sheet.write(3, 1, 'wen')
# 保存到文件中
book.save('test.xls')
程序运行后,可在本地找到该Excel文件,结果如图:
代码说明一下:
(1)导入xlwt库
(2)通过Workbook()方法创建一个工作簿
(3)创建一个名字为Sheet1的工作表
(4)写入数据,可以看出第一个和第二个参数为Excel表格的单元格的位置,第三个为写入内容。
(5)保存到文件
4.2 爬虫思路分析
(1)爬取的内容为起点中文网的全部作品信息(https://www.qidian.com/
),如下图
(2)爬取起点中文网的全部作品信息的前100页,通过手动浏览,下面为第2页的网址
https://www.qidian.com/all?orderId=&style=1&pageSize=20&siteid=1&pubflag=0&hiddenField=0&page=2
https://www.qidian.com/all?orderId=&style=1&pageSize=20&siteid=1&pubflag=0&hiddenField=0&page=3
猜想这些字段是用来控制作品分类的,我们爬取的为全部作品,依次删掉一些参数检查,发现将网址改为https://www.qidian.com/all?page=2
后,也可以访问相同的信息,通过多页检验,证明了修改的合理性,以此来构造前100页URL。
(3)需要爬取的信息有:小说名、作者ID、小说类型、完成情况、摘要和字数。如图:
(4)运用xlwt库,把爬取的信息存储在本地的Excel表格中。
# 起点中文网
import xlwt
import requests
from lxml import etree
import time
import re
from fontTools.ttLib import TTFont
# 初始化列表,存入爬虫数据
all_info_list = []
dict_en = {
'one': '1',
'two': '2',
'three': '3',
'four': '4',
'five': '5',
'six': '6',
'seven': '7',
'eight': '8',
'nine': '9',
'period': '.',
'zero': '0',
}
def get_info(url):
# 定义获取爬虫信息的函数
htmltext = requests.get(url)
# print(html.text)
selector = etree.HTML(htmltext.text)
# 获取字体文件
# /html/body/div[2]/div[5]/div[2]/div[2]/div/ul/li[1]/div[2]/p[3]/span/style
# 获取font-face的第一段信息
font_html = selector.xpath('/html/body/div[2]/div[5]/div[2]/div[2]/div/ul/li[1]/div[2]/p[3]/span/style')[0]
# print(etree.tostring(font_html[0]))
font_face = etree.tostring(font_html)
# print(str(font_face))
font_file_url = re.findall('url\((.*?)\)', str(font_face), re.S)[-1].replace('\'', '')
print(font_file_url)
b = requests.get(font_file_url)
with open('new.ttf', 'wb') as f:
f.write(b.content)
f.close()
font_new = TTFont('new.ttf')
# font_new.saveXML('font_new.xml')
cmap = font_new.getBestCmap()
print(cmap)
# /html/body/div[2]/div[5]/div[2]/div[2]/div/ul/li[1]
# 定位大标签,以此循环
infos = selector.xpath('body/div[2]/div[5]/div[2]/div[2]/div/ul/li')
print(len(infos))
for info in infos:
# title,author,style_1,style_2,style,complete,introduce,word
# /html/body/div[2]/div[5]/div[2]/div[2]/div/ul/li[1] /div[2]/h4/a
title = info.xpath('div[2]/h4/a/text()')[0]
# print(title)
# /html/body/div[2]/div[5]/div[2]/div[2]/div/ul/li[1] /div[2]/p[1]/a[1]
author = info.xpath('div[2]/p[1]/a[1]/text()')[0]
# print(author)
# /html/body/div[2]/div[5]/div[2]/div[2]/div/ul/li[1] /div[2]/p[1]/a[2]
style1 = info.xpath('div[2]/p[1]/a[2]/text()')[0]
# print(style1)
# /html/body/div[2]/div[5]/div[2]/div[2]/div/ul/li[1] /div[2]/p[1]/a[3]
style2 = info.xpath('div[2]/p[1]/a[3]/text()')[0]
# print(style2)
style = style1 + '-' + style2
# print(style)
# /html/body/div[2]/div[5]/div[2]/div[2]/div/ul/li[1] /div[2]/p[1]/span
complete = info.xpath('div[2]/p[1]/span/text()')[0]
# print(complete)
# /html/body/div[2]/div[5]/div[2]/div[2]/div/ul/li[1] /div[2]/p[2]
introduce = info.xpath('div[2]/p[2]/text()')[0]
# print(introduce)
# print(introduce.strip())
# /html/body/div[2]/div[5]/div[2]/div[2]/div/ul/li[1] /div[2]/p[3]/span/span
word_all = info.xpath('div[2]/p[3]/span/span')
# print(word)
# print(str(etree.tostring(word[0], encoding='gbk'))[2:-1])
word_temp = str(etree.tostring(word_all[0]))
# print(word_temp)
word_final = re.findall('">(.*?);</span>', word_temp, re.S)[0]
words = word_final.split(';')
print(words)
word = ''
for char in words:
# print(int(char[2:]))
# print(get_real_num(cmap, int(char[2:])))
# print(cmap[int(char[2:])])
word += get_real_num(cmap, int(char[2:]))
info_list = [title, author, style, complete, introduce, word]
all_info_list.append(info_list)
time.sleep(1)
def get_real_num(dict, code):
return dict_en[dict[code]]
# get_info('https://www.qidian.com/all?page=1')
# print(all_info_list)
if __name__ == '__main__':
# 程序主入口
urls = ['https://www.qidian.com/all?page={}'.format(str(i)) for i in range(1, 3)]
for url in urls:
get_info(url)
header = ['title', 'author', 'style', 'complete', 'introduce', 'word']
# 定义表头
book = xlwt.Workbook(encoding='utf-8')
# 创建工作簿
sheet = book.add_sheet('Sheet1')
# 创建工作表
for h in range(len(header)):
# 写入表头
sheet.write(0, h, header[h])
i = 1
for list in all_info_list:
j = 0
for data in list:
sheet.write(i, j, data)
j += 1
i += 1
book.save('xiaoshuo.xls')
注:代码写于 2018/11/2,因此关于字体反爬更新于此时间段。
程序运行后,将会存入数据到Excel
表格中,如下图: