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 = '''
  • red flowers

  • yellow flowers

  • white flowers

  • black flowers

  • blue flowers
''' html = etree.HTML(text) print(html) # Lxml库解析数据,为Element对象

打印结果如下:

首先导入Lxml中的etree库,然后利用etree.HTML进行初始化,最后把结果打印出来。可以看出,etree库把HTML文档解析为Element对象,可以通过以下代码输出解析过的HTML文档。

from lxml import etree

text = '''
  • red flowers

  • yellow flowers

  • white flowers

  • black flowers

  • blue flowers
''' 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命令,如下图:

Ch5 Lxml库与Xpath语法_第1张图片

新建好的HTML文件,已经自动生成了html、head和body标签,也可以通过单击Pycharm右上角的浏览器符号,在本地打开制作好的HTML文件,如下图:

Ch5 Lxml库与Xpath语法_第2张图片

把前面的字符串复制在HTML文档中,如下图,最后通过浏览器打开制作好的HTML文件。

Ch5 Lxml库与Xpath语法_第3张图片
Ch5 Lxml库与Xpath语法_第4张图片

这样便可通过Lxml库读取HTML文件中的内容了,可以通过下面的代码读取:

from lxml import etree

html = etree.parse('flower.html')
result = etree.tostring(html, pretty_print=True)
print(result)

注:中的/必须要带,不然会解析报错。

  • 解析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元素的父节点。


    xiao ming
    man
    34
    89

  • 子节点
    元素节点可有0个、一个或多个子节点,在下面的例子中,namesexidgoal元素都是user元素的子节点。

    xiao ming
    man
    34
    89

  • 同胞节点
    同胞节点拥有相同的父节点,在下面的例子中,namesexidgoal元素都是同胞节点

    xiao ming
    man
    34
    89

  • 先辈节点
    先辈节点指某节点的父、父的父节点等,在下面的例子中,name元素的先辈是user元素和user_database元素:


    xiao ming
    man
    34
    89


  • 后代节点

后代节点指某个节点的子节点、子节点的子节点等,在下面的例子中,user_database的后代是user、name、sex、id及goal元素:



    xiao ming
    man
    34
    89


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复制得到,如下图:

Ch5 Lxml库与Xpath语法_第5张图片
复制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标签为一个段子信息。

Ch5 Lxml库与Xpath语法_第6张图片
寻找循环点

(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)

# 标签为
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)

有时候会遇到相同的字符开头的多个标签:

  • 需要的内容1
  • 需要的内容2
  • 需要的内容3
  • 想同时爬取时,不需要构造多个Xpath路径,通过starts-with()便可以获取多个标签内容。

    from lxml import etree
    
    html1 = '''
    
  • 需要的内容1
  • 需要的内容2
  • 需要的内容3
  • ''' 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)
    
    # 循环点: 
    # //*[@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)

    当遇到标签套标签情况时:

    需要的内容1

    需要的内容2

    >

    想同时获取文本内容,可以通过string(.)完成:

    from lxml import etree
    
    html2 = '''
    
    需要的内容1

    需要的内容2

    > ''' 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种方法爬取糗事百科文字内容中的信息,如下图:

    Ch5 Lxml库与Xpath语法_第7张图片

    (2)由于是比较性能,爬取的信息并不是很多,爬取的信息有:用户ID、发表段子文字信息、好笑数量和评论数量,如下图:

    Ch5 Lxml库与Xpath语法_第8张图片

    (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('

    (.*?)

    ', res.text, re.S) # print(len(ids)) contents = re.findall('
    .*?(.*?)', res.text, re.S) # print(len(contents)) laughs = re.findall('.*?(\d+)', res.text, re.S) # print(len(laughs)) # print(laughs) # 47 评论 comments = re.findall('(\d+) 评论', 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) #
    # //*[@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文件,用记事本打开,效果如下:

    Ch5 Lxml库与Xpath语法_第9张图片

    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链接、作者、出版社和出版时间,书本价格、评分和评价,如下图:

    Ch5 Lxml库与Xpath语法_第10张图片

    注意:这里只爬了第一作者

    (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打开会出现乱码错误,如图:

    Ch5 Lxml库与Xpath语法_第11张图片
    乱码错误

    可以通过记事本打开,将其另存为编码为UTF-8的文件,便不会出现乱码问题。

    Ch5 Lxml库与Xpath语法_第12张图片

    这时再通过Excel打开文件,便不会出现乱码问题了。

    Ch5 Lxml库与Xpath语法_第13张图片
    解决结果

    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文件,结果如图:

    Ch5 Lxml库与Xpath语法_第14张图片

    代码说明一下:

    (1)导入xlwt库
    (2)通过Workbook()方法创建一个工作簿
    (3)创建一个名字为Sheet1的工作表
    (4)写入数据,可以看出第一个和第二个参数为Excel表格的单元格的位置,第三个为写入内容。
    (5)保存到文件

    4.2 爬虫思路分析

    (1)爬取的内容为起点中文网的全部作品信息(https://www.qidian.com/),如下图

    Ch5 Lxml库与Xpath语法_第15张图片

    (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、小说类型、完成情况、摘要和字数。如图:

    Ch5 Lxml库与Xpath语法_第16张图片

    (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('">(.*?);', 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表格中,如下图:

    Ch5 Lxml库与Xpath语法_第17张图片

    你可能感兴趣的:(Ch5 Lxml库与Xpath语法)