zoukankan      html  css  js  c++  java
  • Python爬虫 | lxml解析html页面

    一、简介

    1.下载:pip install lxml

    推荐使用douban提供的pipy国内镜像服务,如果想手动指定源,可以在pip后面跟-i 来指定源,比如用豆瓣的源来安装web.py框架:

    pip install web.py -i http://pypi.douban.com/simple --trusted-host pypi.douban.com

    2.导包

    from lxml import etree

    3.xpath解析原理:

    • 实例化一个etree对象,然后将即将被解析的页面源码数据加载到该对象中。
    • 通过调用etree对象中的xpath方法,结合着xpath表达式进行标签定位和数据提取

    4.如何实例化一个etree对象:

      将html文档或者xml文档转换成一个etree对象,然后调用对象中的方法查找指定的节点

    • 本地文件:将本地的一个html文档中的数据加载到etree对象中, 使用的比较少
    tree = etree.parse(文件名fileName)
    tree.xpath("xpath表达式")
    • 网络数据:将互联网爬取到的页面源码数据加载到该对象中
    tree = etree.HTML(网页内容字符串page_text)
    tree.xpath("xpath表达式")

    启动和关闭插件 ctrl + shift + x

    二、常用xpath表达式

    首先,本地新建一个html文档,所以要使用etree.parse(fileName)

    <html lang="en">
    <head>
        <meta charset="UTF-8" />
        <title>测试bs4</title>
    </head>
    <body>
        <div>
            <p>百里守约</p>
        </div>
    
        <div class="song">
            <p>李清照</p>
            <p>王安石</p>
            <p>苏轼</p>
            <p>柳宗元</p>
            <a href="http://www.song.com/" title="赵匡胤" target="_self">
                <span>this is span</span>
            宋朝是最强大的王朝,不是军队的强大,而是经济很强大,国民都很有钱</a>
            <a href="" class="du">总为浮云能蔽日,长安不见使人愁</a>
            <img src="http://www.baidu.com/meinv.jpg" alt="" />
        </div>
    
        <div class="tang">
            <ul>
                <li><a href="http://www.baidu.com" title="qing">清明时节雨纷纷,路上行人欲断魂,借问酒家何处有,牧童遥指杏花村</a></li>
                <li><a href="http://www.163.com" title="qin">秦时明月汉时关,万里长征人未还,但使龙城飞将在,不教胡马度阴山</a></li>
                <li><a href="http://www.126.com" alt="qi">岐王宅里寻常见,崔九堂前几度闻,正是江南好风景,落花时节又逢君</a></li>
                <li><a href="http://www.sina.com" class="du">杜甫</a></li>
                <li><a href="http://www.dudu.com" class="du">杜牧</a></li>
                <li><b>杜小月</b></li>
                <li><i>度蜜月</i></li>
                <li><a href="http://www.haha.com" id="feng">凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘</a></li>
            </ul>
        </div>
    </body></html>

    页面显示如下

    层级&索引定位

    #找到class属性值为tang的div的直系子标签ul下的第二个子标签li下的直系子标签a
    //div[@class="tang"]/ul/li[2]/a
    
    下面这三个结果相同
    r = tree.xpath('/html/head/title')
    r = tree.xpath('/html//title')
    r = tree.xpath('//title')

    r = tree.xpath('//p')            # 所有的p标签

     

    标签定位:

    //div[@class="song"]     # 找到class属性值为song的div标签 

    模糊匹配:

    //div[contains(@class, "ng")]       # class属性值包含ng的div
    //div[starts-with(@class, "ta")]    # class属性以ta开头的div

    取属性:

    //div[@class="tang"]//li[2]/a/@href
    
    r = tree.xpath('//div[@class="song"]/img/@src')
    print(r)

    取文本: /text()直系的文本内容  //text()所有的文本内容

    //div[@class="song"]/p[1]/text()     # /表示获取某个标签下的文本内容
    //div[@class="tang"]//text()         # //表示获取某个标签下的文本内容和所有子标签下的文本内容
    # 获得的是列表,只不过里面只有一个元素
    r = tree.xpath('//div[@class="song"]/p[4]/text()')
    print(r)

     

    r = tree.xpath('//div[@class="song"]/p[4]/text()')[0]
    print(r)

    r = tree.xpath('//div[@class="song"]//text()')
    print(r)

     

    逻辑运算

    # 找到href属性值为空且class属性值为du的a标签
    //a[@href="" and @class="du"]

    三、案例

    案例1:解析图片数据:http://pic.netbian.com/4kmeinv/

    查看:网址鼠标悬浮上去会有图片名称,所以爬取图片以及对应的名称,要提前确定不是动态加载的。

     

    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/73.0.3683.103 Safari/537.36'
    }
    url = 'http://pic.netbian.com/4kdongman/'
    
    response = requests.get(url=url,headers=headers)
    # response.encoding = 'utf-8'                                           #手动设定响应数据的编码
    
    
    page_text = response.text
    
    #数据解析(图片地址,图片名称)    
    tree = etree.HTML(page_text)
    li_list = tree.xpath('//div[@class="slist"]/ul/li')        
    
    for li in li_list:
    
        #局部内容解析一定是以./开头。etree和element都可以调用xpath
        img_src = 'http://pic.netbian.com'+li.xpath('./a/img/@src')[0]      # 解析出来的没有域名,要加上
        img_name = li.xpath('./a/img/@alt')[0]                              #不要忘记前面加点号,表示从当前li标签开始
        img_name = img_name.encode('iso-8859-1').decode('gbk')              #处理中文乱码的通用形式
        img_data = requests.get(url=img_src,headers=headers).content
    
        img_path = './qiutuLibs/'+img_name+'.jpg'
        with open(img_path,'wb') as fp:
            fp.write(img_data)
            print(img_name,'下载成功!!!')

    解析:
    1.

    li_list = tree.xpath('//div[@class="slist"]/ul/li')
    print(li_list)                                    # 返回的是一个element类型的数据对象

     2.

     li标签里面有a标签,然后再里面是img标签, 然后有一个src属性和alt属性

    img_src = 'http://pic.netbian.com'+li.xpath('./a/img/@src')[0]                    # 解析出来的没有域名,要加上
    img_name = li.xpath('./a/img/@alt')[0]    

    3. 出现乱码,有两种解决策略:

    (1)对整体设定响应数据的编码

    手动设定响应数据的编码,查看页面是用哪种编码方式是utf-8,还是gbk等。如果这种方式不行,用下面的方式

    response.encoding = 'utf-8'

    (2)针对具体的内容手动设定

    img_name = img_name.encode('iso-8859-1').decode('gbk')     #处理中文乱码的通用形式

    案例2:xpath解析-boss直聘

    import requests
    from lxml import etree
    import json
    
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36'
    }
    
    url = 'https://www.zhipin.com/job_detail/?query=python%E7%88%AC%E8%99%AB&city=101010100&industry=&position='
    page_text = requests.get(url=url, headers=headers).text
    
    # 数据解析:jobName,salary,company,jobDesc
    tree = etree.HTML(page_text)
    li_list = tree.xpath('//div[@class="job-list"]/ul/li')
    job_data_list = []
    for li in li_list:
        job_name = li.xpath('.//div[@class="info-primary"]/h3/a/div/text()')[0]  # 记得后面加[0]
        salary = li.xpath('.//div[@class="info-primary"]/h3/a/span/text()')[0]
        company = li.xpath('.//div[@class="company-text"]/h3/a/text()')[0]
        detail_url = 'https://www.zhipin.com' + li.xpath('.//div[@class="info-primary"]/h3/a/@href')[0]
    
        # 详情页的页面源码数据
        detail_page_text = requests.get(url=detail_url, headers=headers).text
        detail_tree = etree.HTML(detail_page_text)
        job_desc = detail_tree.xpath('//*[@id="main"]/div[3]/div/div[2]/div[2]/div[1]/div//text()')
        job_desc = ''.join(job_desc)
    
        dic = {
            'job_name': job_name,
            'salary': salary,
            'company': company,
            'job_desc': job_desc
        }
        job_data_list.append(dic)
    
    fp = open('job.json', 'w', encoding='utf-8')
    json.dump(job_data_list, fp, ensure_ascii=False)
    fp.close()
    print('over')

     

    解析:

    1. 因为有br标签,所以用//

     

    job_desc = detail_tree.xpath('//*[@id="main"]/div[3]/div/div[2]/div[2]/div[1]/div//text()')
    print(job_desc)

    2. 输出的是列表,里面是元素

     

    所以,字符串拼接

    job_desc = detail_tree.xpath('//*[@id="main"]/div[3]/div/div[2]/div[2]/div[1]/div//text()')
    job_desc = ''.join(job_desc)
    print(job_desc)

    最终文件

     

    案例3:xpath解析-热门城市全国城市名称  https://www.aqistudy.cn/historydata

    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/73.0.3683.103 Safari/537.36'
    }
    url = 'https://www.aqistudy.cn/historydata/'
    page_text = requests.get(url=url,headers=headers).text
    
    tree = etree.HTML(page_text)
    city_list = tree.xpath('//div[@class="bottom"]/ul/li/a/text() | //div[@class="bottom"]/ul/div[2]/li/a/text()')  # 逻辑
    
    #hot_city://div[@class="bottom"]/ul/li/a/text()
    #all_city://div[@class="bottom"]/ul/div[2]/li/a/text()
    print(city_list)
    print(len(city_list))

     

    全部城市: //div[@class="bottom"]/ul/div[2]/li/a/text()

     

     

    案例4:获取好段子中段子的内容和作者http://www.haoduanzi.com

    from lxml import etree
    import requests
    
    url='http://www.haoduanzi.com/category-10_2.html'
    headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36',
        }
    url_content=requests.get(url,headers=headers).text
    tree=etree.HTML(url_content)                                            # 使用xpath解析从网络上获取的数据
    title_list=tree.xpath('//div[@class="log cate10 auth1"]/h3/a/text()')   # 解析获取当页所有段子的标题
    
    ele_div_list=tree.xpath('//div[@class="log cate10 auth1"]')
    
    text_list=[]                                                            # 最终会存储12个段子的文本内容
    for ele in ele_div_list:
        text_list=ele.xpath('./div[@class="cont"]//text()')                 # 段子的文本内容(是存放在list列表中)
        text_str=str(text_list)                                             # list列表中的文本内容全部提取到一个字符串中
        text_list.append(text_str)                                          # 字符串形式的文本内容防止到all_text列表中
    
    print(title_list)
    print(text_list)

    案例5:58二手房

    import requests
    from lxml import etree
    url ='https://bj.58.com/shahe/ershoufang/?utm_source=market&spm=u-2d2yxv86y3v43nkddh1.BDPCPZ_BT&PGTID=0d30000c-0047-e4e6-f587-683307ca570e&ClickID=1'
    headers = {
        'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36'
    }
    page_text = requests.get(url=url,headers=headers).text
    
    tree = etree.HTML(page_text)
    li_list = tree.xpath('//ul[@class="house-list-wrap"]/li')
    fp = open('58.csv','w',encoding='utf-8')
    for li in li_list:
        title = li.xpath('./div[2]/h2/a/text()')[0]
        price = li.xpath('./div[3]//text()')
        price = ''.join(price)
        fp.write(title+":"+price+'
    ')
    fp.close()
    print('over')

     

    案例6:http://pic.netbian.com/4kmeinv/

    import requests
    from lxml import etree
    import os
    import urllib
    
    url = 'http://pic.netbian.com/4kmeinv/'
    headers = {
        'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36'
    }
    response = requests.get(url=url,headers=headers)
    #response.encoding = 'utf-8'
    if not os.path.exists('./imgs'):
        os.mkdir('./imgs')
    page_text = response.text
    
    tree = etree.HTML(page_text)
    li_list = tree.xpath('//div[@class="slist"]/ul/li')
    for li in li_list:
        img_name = li.xpath('./a/b/text()')[0]
        #处理中文乱码
        img_name = img_name.encode('iso-8859-1').decode('gbk')
        img_url = 'http://pic.netbian.com'+li.xpath('./a/img/@src')[0]
        img_path = './imgs/'+img_name+'.jpg'
        urllib.request.urlretrieve(url=img_url,filename=img_path)
        print(img_path,'下载成功!')
    print('over!!!')

    案例7下载煎蛋网中的图片数据:http://jandan.net/ooxx【重点】src加密*****

    import requests
    from lxml import etree
    from fake_useragent import UserAgent
    import base64
    import urllib.request
    
    url = 'http://jandan.net/ooxx'
    ua = UserAgent(verify_ssl=False,use_cache_server=False).random
    headers = {
        'User-Agent':ua
    }
    page_text = requests.get(url=url,headers=headers).text
    tree = etree.HTML(page_text)        
    
    
    #在抓包工具的数据包响应对象对应的页面中进行xpath的编写,而不是在浏览器页面中。
    #获取了加密的图片url数据
    imgCode_list = tree.xpath('//span[@class="img-hash"]/text()')
    
    imgUrl_list = []
    for url in imgCode_list:
        img_url = 'http:'+base64.b64decode(url).decode()    #base64.b64decode(url)为byte类型,需要转成str
    imgUrl_list.append(img_url)
    
    for url in imgUrl_list:
        filePath = url.split('/')[-1]
        urllib.request.urlretrieve(url=url,filename=filePath)
        print(filePath+'下载成功')

    查看页面源码:发现所有图片的src值都是一样的。简单观察会发现每张图片加载都是通过jandan_load_img(this)这个js函数实现的。在该函数后面还有一个class值为img-hash的标签,里面存储的是一组hash值,该值就是加密后的img地址加密就是通过js函数实现的,所以分析js函数,获知加密方式,然后进行解密。

    通过抓包工具抓取起始url的数据包,在数据包中全局搜索js函数名(jandan_load_img),然后分析该函数实现加密的方式。在该js函数中发现有一个方法调用,该方法就是加密方式,对该方法进行搜索搜索到的方法中会发现base64和md5等字样,md5是不可逆的所以优先考虑使用base64解密

     

     

     

     

     

     

     

     

     

     

     

    案例7爬取站长素材中的简历模板

    import requests
    import random
    from lxml import etree
    headers = {
        'Connection':'close',                             # 当请求成功后,马上断开该次请求(及时释放请求池中的资源)
        'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36'
    }
    url = 'http://sc.chinaz.com/jianli/free_%d.html'
    for page in range(1,4):                            # 因为第一页和其他页url格式不一样,所以分情况讨论    
        if page == 1:
            new_url = 'http://sc.chinaz.com/jianli/free.html'
        else:
            new_url = format(url%page)
        
        response = requests.get(url=new_url,headers=headers)
        response.encoding = 'utf-8'                        # 中文乱码,先调整编码方式
        page_text = response.text
    
        tree = etree.HTML(page_text)
        div_list = tree.xpath('//div[@id="container"]/div')
        for div in div_list:
            detail_url = div.xpath('./a/@href')[0]
            name = div.xpath('./a/img/@alt')[0]
    
            detail_page = requests.get(url=detail_url,headers=headers).text
            tree = etree.HTML(detail_page)
            download_list  = tree.xpath('//div[@class="clearfix mt20 downlist"]/ul/li/a/@href')    # 这样获得的是每个的所有下载链接
            download_url = random.choice(download_list)             # 为了防止每个链接因请求过于频繁被禁,随机选择一个
            data = requests.get(url=download_url,headers=headers).content
            fileName = name+'.rar'
            with open(fileName,'wb') as fp:
                fp.write(data)
                print(fileName,'下载成功')

    Alt里面的图片名称是中文,要注意打印看一下会不会有乱码

     

     

    有乱码,尝试用第一种方式是否可以解决,可以解决就不用第二种方式

    详情页中每个li标签对应一个下载地址

    li标签里有一个a

     

  • 相关阅读:
    html——meta标签、link标签
    html——a标签中target属性
    html——相对路径、绝对路径(有待补充....)
    CSS——display:flex
    JS——AJAX
    JS——锚点的运用
    Json——转义符
    C#——工厂模式
    C#——反射动态创建类的实例
    “微信跳一跳”辅助脚本的一种简单实现
  • 原文地址:https://www.cnblogs.com/Summer-skr--blog/p/11402076.html
Copyright © 2011-2022 走看看