python 爬虫(七)lxml模块 + lxml数据提取(字符串的xml/html文件--转换--element对象--转换--字符串)调用XPath方法筛选数据 + 案例(扇贝 酷狗 网易云音乐)

爬虫小知识:爬取网站流程

  1. 确定网站哪个url是数据的来源。
  2. 简要分析一下网站结构,查看数据一般放在哪里。
  3. 查看是否有分页,解决分页的问题。
  4. 发送请求,查看response.text里面是否有我们想要的数据内容。
  5. 如果有数据,就用相应的提取数据的方法提取数据保存。

注意:1.刚开做爬虫项目,先不要用类去做,只需要关注数据的来源等问题的解决,不要关注类结构的设计。

一、lxml模块


1. lxml简介与安装


简介: lxml 是一个HTML/XML的解析器,主要的功能是如何解析和提取 HTML/XML 数据。

lxml和正则一样,也是用 C 实现的,是一款高性能的 Python HTML/XML 解析器,我们可以利用之前学习的XPath语法,来快速的定位特定元素以及节点信息。

lxml python 官方文档: http://lxml.de/index.html

需要安装C语言库,可使用 pip 安装:pip install lxml (或通过wheel方式安装)

在这里我们用的Anaconda编译器,不需再安装


2. lxml初步使用


我们利用它来解析 HTML 代码


1. 导包


from lxml import etree

2.xml转成element对象的方法


(1)将字符串形式的xml内容解析成可以调用xpath语法的element对象的方法


  • 格式:
from lxml import etree
html_element = etree.HTML(html_str)
  • 方法: etree.HTML(xml字符串) 解析的element对象是html格式
 etree.HTML(xml字符串)  
  • 案例: 返回值-------------->element对象
    在这里插入图片描述

  • etree.HTML()可以帮我们补齐标签
    在这里插入图片描述

小结:lxml可以自动修正html代码


3. 将一个html文件转化成element对象的方法


  • 格式:
from lxml import etree
		html = etree.parse('demo.html')
		# print(html)
  • 方法: etree.parse(‘html文件’) 解析的element是xml格式,要注意标签头尾对应
etree.parse('html文件')
  • 案例:
    在这里插入图片描述
    注意:从文件中读取数据,要求文件内容符合xml格式,如果标签缺失,则不能正常读取。

4. 如何将element对象转化成字符串的方法


格式:

#初始化一个xpath解析对象
html = etree.HTML(text)

result = etree.tostring(html,encoding='utf-8')  ##解析对象输出代码 是一个bytes类型
print(type(html))  #<class 'lxml.etree._Element'>
print(type(result))  #<class 'bytes'>
print(result.decode('utf-8'))  # 输出字符串
## pretty_print=True 格式化输出,美观数据

print(etree.tostring(element对象,pretty_print=True,encoding='utf-8').decode('utf-8'))
  • 不解码是bytes类型:
    在这里插入图片描述

  • 案例:.decode(‘utf-8’)写在括号里面(不会加上html标签)
    在这里插入图片描述

  • 案例:.decode(‘utf-8’)写在括号外面(加html标签)
    在这里插入图片描述


二、在python中如何使用XPATH


通过lxml模块,可以使用xpath语法来筛选元素(借用lxml模块返回的element对象)


1. 使用xpath语法筛选元素


  • 返回值element还可以继续调用xpath方法来继续筛选元素;

  • element对象.xpath() 方法返回的是一个列表

  • 格式:

html_element.xpath()
  • 案例:加xpath来筛选元素

在这里插入图片描述
在这里插入图片描述

  • xpath获取标签返回的是列表,列表里面是element对象
    在这里插入图片描述

2. 筛选元素各种方法实例


要特别注意的是这一串代码!!!!!!!!!

result = point.xpath('//bookstore/book[position()<3]/title/text()')  ## 必须指定哪个节点,用/或者不写都取不出数据,因为他不能定位到bookstore,XML并不是我们看到的这样的结构前面还有标签
  • 代码
from lxml import etree
html = '''
<bookstore>
    <title>新华书店</title>
    <book href="http://www.langlang2017.com/">
        <title lang="eng">Harry Potter</title>
        <price>29.99</price>
    </book>
    <book>
        <title lang="zh">Learning XML</title>
        <price>39.95</price>
    </book>
    <book href="www.baidu.com">
        <title>python 大全</title>
        <price>99.95</price>
    </book>
</bookstore>
'''
point = etree.HTML(html)

# (1)获取文档中的所有book节点的第一个book节点
result = point.xpath('//book[1]')
# (2)获取第一个book节点
first_book = result[0]  ## 上题获取的是列表,这题获取的是element对象
print(first_book)
# (3)获取first_book当前节点下的href
result = first_book.xpath('./@href')  ##  当前节点或者父节点下的标签用/和//效果一样,定位到bookstore下了,取得都是book,除非下面还有后代节点才有区别
# (4)获取当前节点的父节点下的book节点的href属性
result = first_book.xpath('../book/@href')
# (5)获取价格小于40的书的书名
result = point.xpath('//book[price<40]/title/text()')
# (6)获取价格等于99.95的书的书名
result = point.xpath('//book[price=99.95]/title/text()')
# (7)获取book标签下面的title和price标签
result = point.xpath('//book/title|//book/price')
# (8)获取属性href含有baidu字符串的book标签,获取此标签的书名
result = point.xpath('//book[contains(@href,"baidu")]/title/text()')
# (9)获取去前面两个属于bookstore标签的字标签的book标签,获取此标签的书名
result = point.xpath('//bookstore/book[position()<3]/title/text()')  ## 必须指定哪个节点,用/或者不写都取不出数据,因为他不能定位到bookstore,XML并不是我们看到的这样的结构前面还有标签
# (10)匹配任何属性节点
# result = point.xpath('//*[@*]')
# (11)获取所有带有属性title标签,获取此标签的内容
# result = point.xpath('//title[@*]/text()')
print(result)

三、element对象调用xpath方法筛选数据案例


1. 案例:获取扇贝单词中的python必背词汇


  • 爬取内容
    在这里插入图片描述

  • 注意:分页的实现

  • 代码

  • 普通函数写法

import json

import requests
from lxml import etree

## 确定url
base_url = 'https://www.shanbay.com/wordlist/110521/232414/?page=%s'

## 封装headers
headers = {
    'user-agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36'
}

def get_text(value):
    if value:
        return value[0]
    else:
        return ''

words = []
## 分页
for i in range(1,4):
## 发送请求
    response = requests.get(base_url %i,headers=headers)
    # print(response.text)
## 如果有数据,就用相应的方法提取数据
    ## 获取element对象来使用xpath方法提取数据
    html = etree.HTML(response.text)
    tr_list = html.xpath('//tbody/tr')
    ## 从获取的列表中的tr获取数据
    for tr in tr_list:
        en = get_text(tr.xpath('.//td[class="span2"]/strong/text()'))
        # en = tr.xpath('.//td[contains(@class,"span2")]/strong/text()')
        zh = get_text(tr.xpath('.//td[@class="span10"]/text()'))

        ### {'recipe':n. 挂起,暂停}
        # items()
        item = {}
        if en and zh:
            item[en] = zh
            words.append(item)

with open('word.json','w',encoding='utf-8') as fp:
    json.dump(words,fp)
  • 封装成面向对象
import json
import requests
from lxml import etree

class Shanbei(object):
    def __init__(self,url,headers):
        self.words = []
        self.headers = headers
        self.url = url
        self.parse()

    def get_text(self,value):
        if value:
            return value[0]
        else:
            return ''
    def parse(self):
    	### 分页
        for i in range(1, 4):
            ## 发送请求
            response = requests.get(self.url % i, self.headers)
            # print(response.text)
            ## 如果有数据,就用相应的方法提取数据
            ## 获取element对象来使用xpath方法提取数据
            html = etree.HTML(response.text)
            tr_list = html.xpath('//tbody/tr')
            ## 从获取的列表中的tr获取数据
            for tr in tr_list:
                en = self.get_text(tr.xpath('.//td[class="span2"]/strong/text()'))
                # en = self.get_text(tr.xpath('.//td[contains(@class,"span2")]/strong/text()'))
                print(en)
                zh = self.get_text(tr.xpath('.//td[@class="span10"]/text()'))

                ### {'recipe':n. 挂起,暂停}
                # items()
                item = {}
                if en and zh:
                    item[en] = zh
                    self.words.append(item)


if __name__ == '__main__':
    base_url = 'https://www.shanbay.com/wordlist/110521/232414/?page=%s'
    headers = {
        'user-agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36'
    }
    sb = Shanbei(base_url,headers)

    with open('word.json','w',encoding='utf-8') as fp:
        json.dump(sb.words,fp)

with open('word.json','r') as fp:
    list = json.load(fp)
    for one in list:
        print(one)
  • 运行结果
    在这里插入图片描述

2. 案例:获取网易云音乐歌手信息


  • 页面内容
    在这里插入图片描述

  • 注意:推荐页上面没有A,B,C等而且数据重复,热门也不符合我们对于大部分数据的处理方法,我们可以去掉

  • 代码

  • 普通函数写法

import json
import requests
from lxml import etree

base_url = 'https://music.163.com/discover/artist'

### 通过url获取页面的xpath对象以便提取数据
def get_xpath(url):

    headers = {
        'user-agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36'
               }

    response = requests.get(url,headers=headers)
    return etree.HTML(response.text)

singer_infos = []
## 获取简介
def parse_detail(url,item):
    html = get_xpath(url)
    desc_list = html.xpath('//div[@class="n-artdesc"]/p/text()')
    desc = ''.join(desc_list)
    item['desc'] = desc
    singer_infos.append(item)

###  第三步:通过传过来的A,B,Curl来提取对应页面的数据
def parse_type(url):
    ## 获取该页面的xpath
    html = get_xpath(url)
    singer_names = html.xpath('//ul[@id="m-artist-box"]/li/p/a/text()')
    singer_urls = html.xpath('//ul[@id="m-artist-box"]/li/p/a[1]/@href|//ul[@id="m-artist-box"]/li/a/@href')

    for i,name in enumerate(singer_names):
        item = {}
        item["歌手"] = name
        item['歌手连接'] = 'https://music.163.com'+singer_urls[i].strip()
        item['歌手详情页'] = 'https://music.163.com'+singer_urls[i].replace(r'?id','/desc?id').strip()
        # print(item)
        url = 'https://music.163.com'+singer_urls[i].replace(r'?id','/desc?id').strip()
        parse_detail(url,item)

### 第二步:解析每一个地区歌手页面
def parse_area(url):

    html = get_xpath(url)
    ## 获取A,B,C分类的url
    # type_name_url = html.xpath('//ul[@id="initial-selector"]/li[position()>1]/a/@href')

    type_name_url = html.xpath('//ul[@id="initial-selector"]/li[position()>1]/a/@href')
    # print(type_name_url)
    ## 找到A,B,Curl
    for type in type_name_url:
        ## 拼接url
        url = 'https://music.163.com'+type
        parse_type(url)

### 第一步:首页的解析,获取地区列表的url
def parse():
    html = get_xpath(base_url)
    # print(html)
    area_singer_urls = html.xpath('//ul[@class="nav f-cb"]/li/a/@href')

    #################### 去掉热门和推荐url###############
    area_singer_urls = [x for x in area_singer_urls if 'id' in x]
    ################ 拿到剩下的url #################
    for url in area_singer_urls:

        new_url = 'https://music.163.com'+url
        parse_area(new_url)


if __name__ == '__main__':
    parse()
    with open('singer.json','w',encoding='utf-8') as fp:
        json.dump(singer_infos,fp)

with open('singer.json','r') as fp:
    print(json.load(fp))
  • 封装成面向对象
import json
import requests
from lxml import etree

class Music_163:
    def __init__(self,url):
        self.url = url
        self.singer_infos = []

    ### 通过url获取页面的xpath对象以便提取数据
    def get_xpath(self,url):
        headers = {
            'user-agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36'
        }

        response = requests.get(url, headers=headers)
        return etree.HTML(response.text)

    ## 获取简介
    def parse_detail(self,url, item):
        html = self.get_xpath(url)
        desc_list = html.xpath('//div[@class="n-artdesc"]/p/text()')
        desc = ''.join(desc_list)
        print(desc)
        item['desc'] = desc
        self.singer_infos.append(item)

    ###  第三步:通过传过来的A,B,Curl来提取对应页面的数据
    def parse_type(self,url):
        ## 获取该页面的xpath
        html = self.get_xpath(url)
        singer_names = html.xpath('//ul[@id="m-artist-box"]/li/p/a/text()')
        singer_urls = html.xpath('//ul[@id="m-artist-box"]/li/p/a[1]/@href|//ul[@id="m-artist-box"]/li/a/@href')

        for i, name in enumerate(singer_names):
            item = {}
            item["歌手"] = name
            item['歌手连接'] = 'https://music.163.com' + singer_urls[i].strip()
            item['歌手详情页'] = 'https://music.163.com' + singer_urls[i].replace(r'?id', '/desc?id').strip()
            # print(item)
            url = 'https://music.163.com' + singer_urls[i].replace(r'?id', '/desc?id').strip()
            self.parse_detail(url, item)

    ### 第二步:解析每一个地区歌手页面
    def parse_area(self,url):

        html = self.get_xpath(url)
        ## 获取A,B,C分类的url
        type_name_url = html.xpath('//ul[@id="initial-selector"]/li[position()>1]/a/@href')
        # print(type_name_url)
        ## 找到A,B,Curl
        for type in type_name_url:
            ## 拼接url
            url = 'https://music.163.com' + type
            self.parse_type(url)
    ### 第一步:首页的解析,获取地区列表的url
    def parse(self):
        html = self.get_xpath(self.url)
        # print(html)
        area_singer_urls = html.xpath('//ul[@class="nav f-cb"]/li/a/@href')

        #################### 去掉热门和推荐url###############
        area_singer_urls = [x for x in area_singer_urls if 'id' in x]
        ################ 拿到剩下的url #################
        for url in area_singer_urls:
            new_url = 'https://music.163.com' + url
            self.parse_area(new_url)

if __name__ == '__main__':
    base_url = 'https://music.163.com/discover/artist'
    m = Music_163(base_url)
    m.parse()
    with open('singer.json', 'w', encoding='utf-8') as fp:
        json.dump(m.singer_infos, fp)


with open('singer.json','r') as fp:
    print(json.load(fp))
  • 运行结果
    在这里插入图片描述

3. 案例:获取酷狗音乐的歌手信息


  • 页面内容
    在这里插入图片描述
  • 注意:分页的实现;热门和全部歌手重复,可以去掉
  • 代码
  • 普通函数方法
# 获取酷狗音乐的歌手信息
import json
import requests
from lxml import etree

base_url = 'https://www.kugou.com/yy/html/singer.html'

def get_html(url):
    headers = {'user-agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36'}
    response = requests.get(url,headers = headers)
    return response.text

info_list = []
def detail_parse(url,item):
    html = etree.HTML(get_html(url))
    ## 获取歌手信息
    info = html.xpath('.//div[@class="intro"]/p/text()')
    # print(info)
    item["歌手信息"] = info
    info_list.append(item)

## 第三步:通过A的url找到下面对应的歌手信息
def singer_parse(url):
    html = etree.HTML(get_html(url))
    ## 上面一部分有图的歌手
    head_li = html.xpath('.//ul[@id="list_head"]/li')
    ## 爬取没有图像的歌手
    list1_li = html.xpath('.//ul[@class="list1"]/li')
    list = head_li + list1_li
    # print(head_li)
    for li in list:
        item = {}
        if li.xpath('..//strong'):
            singer_name = li.xpath('.//strong/a/text()')[0]
            singer_url = li.xpath('.//strong/a/@href')[0]
            item["歌手"] = singer_name
            item["歌手链接"] = singer_url
        else:
            singer_name = li.xpath('.//a/text()')[0]
            singer_url = li.xpath('.//a/@href')[0]
            item["歌手"] = singer_name
            item["歌手链接"] = singer_url
        print(item)
        detail_parse(singer_url, item)

# 第二步:通过url来找到页面中每个地区A,B,C的url
def area_url(url):
    url = url.replace("/index/1",'/index/%s')
    print(url)
    for i in range(1,7):
        html = etree.HTML(get_html(url %i))
        a_list = html.xpath('.//div[@class="num"]/a[position()>1]/@href')
        print(a_list)
        for url in a_list:
            singer_parse(url)

## 第一步刷选去除全部歌手,选取 地区歌手url
def parse():
    html = etree.HTML(get_html(base_url))
    li_list = html.xpath('.//ul[contains(@class,"sng")]/li[position()>1]')
    # print(len(li_list))
    for li in li_list:
        url = li.xpath('./a/@href')[0]
        # print(url)
        area_url(url)

if __name__ == '__main__':
    parse()
    with open("kg.json",'w',encoding='utf-8') as fp:
        json.dump(info_list,fp)
  • 面向对象封装
# 获取酷狗音乐的歌手信息
import json
import requests
from lxml import etree
class Kg:
    def __init__(self,url):
        self.url = url
        self.info_list = []

    def get_html(self,url):
        headers = {'user-agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36'}
        response = requests.get(url,headers = headers)
        return response.text


    def detail_parse(self,url,item):
        html = etree.HTML(self.get_html(url))
        ## 获取歌手信息
        info = html.xpath('.//div[@class="intro"]/p/text()')
        print(info)
        item["歌手信息"] = info
        self.info_list.append(item)

    ## 第三步:通过A的url找到下面对应的歌手信息
    def singer_parse(self,url):
        html = etree.HTML(self.get_html(url))
        ## 上面一部分有图的歌手
        head_li = html.xpath('.//ul[@id="list_head"]/li')
        ## 爬取没有图像的歌手
        list1_li = html.xpath('.//ul[@class="list1"]/li')
        list = head_li + list1_li
        # print(head_li)
        for li in list:
            item = {}
            if li.xpath('..//strong'):
                singer_name = li.xpath('.//strong/a/text()')[0]
                singer_url = li.xpath('.//strong/a/@href')[0]
                item["歌手"] = singer_name
                item["歌手链接"] = singer_url
            else:
                singer_name = li.xpath('.//a/text()')[0]
                singer_url = li.xpath('.//a/@href')[0]
                item["歌手"] = singer_name
                item["歌手链接"] = singer_url
            # print(item)
            self.detail_parse(singer_url, item)

    # 第二步:通过url来找到页面中每个地区A,B,C的url
    def area_url(self,url):
        url = url.replace("/index/1",'/index/%s')
        print(url)
        for i in range(1,7):
            html = etree.HTML(self.get_html(url %i))
            a_list = html.xpath('.//div[@class="num"]/a[position()>1]/@href')
            print(a_list)
            for url in a_list:
                self.singer_parse(url)

    ## 第一步刷选去除全部歌手,选取 地区歌手url
    def parse(self):
        html = etree.HTML(self.get_html(self.url))
        li_list = html.xpath('.//ul[contains(@class,"sng")]/li[position()>1]')
        # print(len(li_list))
        for li in li_list:
            url = li.xpath('./a/@href')[0]
            # print(url)
            self.area_url(url)

if __name__ == '__main__':
    base_url = 'https://www.kugou.com/yy/html/singer.html'
    k = Kg(base_url)
    k.parse()
    # with open("kg.json",'w',encoding='utf-8') as fp:
    #     json.dump(k.info_list,fp)
  • 运行结果
    在这里插入图片描述

发布了107 篇原创文章 · 获赞 43 · 访问量 6145

猜你喜欢

转载自blog.csdn.net/langdei/article/details/102829216
今日推荐