Ch5 Lxml库与Xpath语法

概要


Lxml库是基于libxml2XML解析库Python封装。该模块使用C语言编写,解析速度比BeautifulSoup更快。Lxml库使用Xpath语法解析定位网页数据。

将讲解Lxml库在MacLinux环境中的安装方法,还将介绍Lxml库的使用方法及Xpath的语法知识,而且通过案例对正则表达式BeautifulSoupLxml进行性能对比。

主要涉及的知识点:

  • Lxml库:学会各个系统下Lxml库的安装和使用方法。

  • Xpath语法:学会Xpath语法并通过Xpath语法提取所需的网页信息。

  • 性能对比:通过案例对正则表达式BeautifulSoupLxml进行性能对比。

  • Requests和Lxml库组合应用:演示如何利用这两大库进行爬虫的方法和技巧。


1. Lxml库的安装与使用方法

Lxml库解析网页数据快,但安装过程却相对困难。主要讲解Lxml库在MacLinux环境中的安装方法及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解析后的文档

这里体现了Lxml库一个非常使用的功能就是自动修正HTML代码,应该注意到了最后一个li标签,其实是把尾标签删掉了,是不闭合的。不过Lxml因为集成了libxml2的特性,具有自动修正HTML代码的功能,这里不仅补齐了li标签,而且还添加了htmlbody标签。

  • 读取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元素是namesexidgoal元素的父节点。

<user>
    <name>xiao ming</name>
    <sex>man</sex>
    <id>34</id>
    <goal>89</goal>
</user>
  • 子节点
    元素节点可有0个、一个或多个子节点,在下面的例子中,namesexidgoal元素都是user元素的子节点。
<user>
    <name>xiao ming</name>
    <sex>man</sex>
    <id>34</id>
    <goal>89</goal>
</user>
  • 同胞节点
    同胞节点拥有相同的父节点,在下面的例子中,namesexidgoal元素都是同胞节点
<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复制得到,如下图:

复制Xpath

(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(.)方法可用于标签套标签情况
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表格中,如下图:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,179评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,229评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,032评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,533评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,531评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,539评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,916评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,813评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,568评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,654评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,354评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,937评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,918评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,152评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,852评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,378评论 2 342

推荐阅读更多精彩内容