Python网络爬虫(数据解析-xpath模块)

在xpath定位时,如果定位的xpath路径中存在tbody标签时,需跳过tbody标签,否则不能拿到结果(例如:./tr/div/tbody/span/a/text() --> 省略tbody即可 --> ./tr/div//span/text() )

一、实现数据爬取的流程

  1. 指定url
  2. 基于requests模块发起请求
  3. 获取响应对象中的数据
  4. 数据解析
  5. 进行持久化存储

  在持久化存储之前需要进行指定数据解析。因为大多数情况下的需求,我们都会指定去使用聚焦爬虫,也就是爬取页面中指定部分的数据值,而不是整个页面的数据。

二、模块安装

# 安装lxml
pip install lxml

# 创建一个etree对象
from lxml import etree

tree = etree.parse(fileName) # fileName 是一个本地的html文件
tree = etree.HTML(page_text) # page_text 是网页获取的str类型字符串

三、常用xpath语法

  测试页面,test.html

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

  常见用法:

  注意:

  • xpath方法返回值永远是一个列表
  • 索引定位中:索引值是从1开始
基于标签定位:
  tree.xpath('/html/head/meta')
  tree.xpath('//meta')
  xpath表达式中最左侧的/和//的区别是什么?
  • /表示我们必须从根标签进行定位
  • //表示我们可以从任意位置标签定位

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

层级
&索引定位: #找到class属性值为tang的div的直系子标签ul下的第二个子标签li下的直系子标签a //div[@class="tang"]/ul/li[2]/a

  tree.xpath('//div[@class="tang"]/ul/li[3]')
  tree.xpath('//div[@class="tang"]//li[3]') # 与上一条属性索取为相同结果
  在xpath表达式中非最左侧的/和//的区别?
  •  /表示一个层级
  •  //表示多个层级

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

模糊匹配:
//div[contains(@class, "ng")] //div[starts-with(@class, "ta")]

取文本:
# /表示获取某个标签下的文本内容 # //表示获取某个标签下的文本内容和所有子标签下的文本内容 //div[@class="song"]/p[1]/text() //div[@class="tang"]//text()

取属性:
//div[@class="tang"]//li[2]/a/@href

四、项目实战(好段子网站)

  项目需求:获取好段子中段子的内容和作者   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 #使用xpath对url_conten进行解析 #使用xpath解析从网络上获取的数据 tree=etree.HTML(url_content) #解析获取当页所有段子的标题 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: #段子的文本内容(是存放在list列表中) text_list=ele.xpath('./div[@class="cont"]//text()') #list列表中的文本内容全部提取到一个字符串中 text_str=str(text_list) #字符串形式的文本内容防止到all_text列表中 text_list.append(text_str)
print(title_list)

五、项目实战(煎蛋网)

  需求:下载煎蛋网中的图片数据:http://jandan.net/ooxx

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

#查看页面源码:发现所有图片的src值都是一样的。
#简单观察会发现每张图片加载都是通过jandan_load_img(this)这个js函数实现的。
#在该函数后面还有一个class值为img-hash的标签,里面存储的是一组hash值,该值就是加密后的img地址
#加密就是通过js函数实现的,所以分析js函数,获知加密方式,然后进行解密。
#通过抓包工具抓取起始url的数据包,在数据包中全局搜索js函数名(jandan_load_img),然后分析该函数实现加密的方式。
#在该js函数中发现有一个方法调用,该方法就是加密方式,对该方法进行搜索
#搜索到的方法中会发现base64和md5等字样,md5是不可逆的所以优先考虑使用base64解密
#print(page_text)

tree = etree.HTML(page_text)
#在抓包工具的数据包响应对象对应的页面中进行xpath的编写,而不是在浏览器页面中。
#获取了加密的图片url数据
imgCode_list = tree.xpath('//span[@class="img-hash"]/text()')
imgUrl_list = []
for url in imgCode_list:
    #base64.b64decode(url)为byte类型,需要转成str
    img_url = 'http:'+base64.b64decode(url).decode()
    imgUrl_list.append(img_url)

for url in imgUrl_list:
    filePath = url.split('/')[-1]
    urllib.request.urlretrieve(url=url,filename=filePath)
    print(filePath+'下载成功')
参考代码
import requests
from lxml import etree

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3573.0 Safari/537.36"
}

url = "http://jandan.net/ooxx/page-%d"

for page in range(1, 5):
    new_url = format(url%page)
    
    page_text = requests.get(url=new_url, headers=headers).text
    
    tree = etree.HTML(page_text)
    
    li_list = tree.xpath("//ol[@class='commentlist']/li")
    
    for li in li_list:
        # 获取文章标题
        title = li.xpath(".//div[@class='author']/strong/text()")
        # 图片地址
        img_url = "http:"+li.xpath(".//div[@class='text']/p/img/@src")[0]
        
        print(title, img_url)
        break

六、项目实战(糗事百科)

  项目需求:获取糗事百科标题与文章内容 ,网址:url:https://www.qiushibaike.com/text/page/1/

import requests
from lxml import etree

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3573.0 Safari/537.36"
}


url = "https://www.qiushibaike.com/text/page/%d/"

# 爬取前5页内容
for page in range(1, 5):
    new_url = format(url%page)
    
    page_text = requests.get(url=new_url, headers=headers).text
    
    # 生成etree对象
    tree = etree.HTML(page_text)
    
    # 获取所有li标签
    div_list = tree.xpath("//div[@class='col1']/div")
    
    for div in div_list:
        # | 管道符 满足其中一个条件即可爬取
        title = div.xpath("./div/a[2]/h2/text() | ./div/span[2]/h2/text()")[0]  # 爬取用户名
        
        # 爬取内容详情
        content = div.xpath("./a[@class='contentHerf']//span/text()")
        
        print(title, content)
    

七、项目实战(BOOS直聘)

import requests
from lxml import etree

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3573.0 Safari/537.36"
}

url = "https://www.zhipin.com/c101010100/?query=python爬虫page=%d"

for page in range(1, 3):
    new_url = format(url%page)
    page_text = requests.get(url=new_url, headers=headers).text

    # 生成etree对象
    tree = etree.HTML(page_text)
    # 获取页面中所有li标签
    li_list = tree.xpath("//div[@class='job-list']/ul/li")
    
    fp = open("boos.txt", "a", encoding="utf-8")
    for li in li_list:
        # 职位标题
        job_title = li.xpath(".//a/div[1]/text()")[0]
        # 薪资
        salary = li.xpath(".//a/span/text()")[0]
        # 职位详情页url
        detail_url = "https://www.zhipin.com"+li.xpath(".//a/@href")[0]
        # 公司名字
        company_title = li.xpath(".//div[@class='company-text']//a/text()")[0]
        
        # 职位详情页
        detail_page_text = requests.get(url=detail_url, headers=headers).text
        detail_tree = etree.HTML(detail_page_text)
        
        # 职位描述详情
        job_desc = detail_tree.xpath("//div[@class='job-sec']/div/text()")
        job_desc = "".join(job_desc)
        
        # 文件保存
        fp.write(job_title+":"+salary+":"+company_title+":"+job_desc+"\n\n\n")
        print(job_title, salary, company_title)
        print(job_desc)
        print(company_title," 公司数据下载完成!!!")

    fp.close()

八、项目实战(彼岸图网,局部中文乱码处理)

import requests
from lxml import etree

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3573.0 Safari/537.36"
}

url = "http://pic.netbian.com/4kmeinv/index_%d.html"

for page in range(1, 3):
    if page == 1:
        new_url = "http://pic.netbian.com/4kmeinv/index.html"
    new_url = format(url%page)
    
    # 返回请求状态 200 OK
    response = requests.get(url=new_url, headers=headers)
    # 在全局处理中文乱码,开销大
    #response.encoding = "gbk"  # 方式一
    #response.encode("iso-8859-1").decode("gbk")  # 方式二
    
    # 获取网页详情
    page_text = response.text
    
    # 生成etree对象
    tree = etree.HTML(page_text)
    # 获取所有的li标签
    li_list = tree.xpath("//ul[@class='clearfix']/li")
    for li in li_list:
        # 获取图片url
        img_url = "http://pic.netbian.com"+li.xpath("./a/img/@src")[0]
        
        # 图片人物的名字
        img_name = li.xpath("./a/b/text()")[0]
        # 局部处理中文乱码,推荐
        img_name = img_name.encode("iso-8859-1").decode("gbk")
        
        print(img_url, img_name)
        break

九、项目实战(xpath管段符)

  需求:爬取所有的城市名称 url:https://www.aqistudy.cn/historydata/

import requests
from lxml import etree

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3573.0 Safari/537.36"
}

url = "https://www.aqistudy.cn/historydata/"

page_text = requests.get(url=url, headers=headers).text

tree = etree.HTML(page_text)

# 方式一 单独获取热门城市 & 所有城市名称
hot_cities = tree.xpath("//div[@class='hot']//li/a/text()")
all_cities = tree.xpath("//div[@class='all']//li/a/text()")

print(hot_cities, all_cities)


# 方式二 通过管道符 获取热门城市名称+所有城市名称
citys = tree.xpath("//div[@class='hot']//li/a/text() | //div[@class='all']//li/a/text()")

print(citys)

 十、补充(scrapy框架中的xpath语法)

  scrapy中的xpath语法:

    response.xpah(语法)返回的是一个选择器对象,需要通过extract()提取

    extract()   -- >  提取结果的是列表对象(复数)

    extract_first()   -->  提取结果是一个字符串,是对列表中的第一个selector对象进行提取

  普通xpath语法:

    返回的是一个列表或者字符串

  scrapy爬虫文件示例:

import scrapy
from ..items import ProxiaohuawangItem


class FirstSpider(scrapy.Spider):
    # 应用名字,爬虫的唯一标识
    name = 'first'
    # 允许访问的域名, 推荐不使用
    # allowed_domains = ['www.xxx.com']
    # 爬虫访问的url列表
    start_urls = ['http://www.xiaohuar.com/hua/']

    # 数据解析
    def parse(self, response):
        div_list = response.xpath('//*[@id="list_img"]/div/div[1]/div')
        for div in div_list:
            # name = div.xpath('./div//span/a/text()')[0].extract()  # 提取方式一
            name = div.xpath('./div//span/a/text()').extract_first()  # 提取方式二
            img_src = div.xpath('./div//a/img/@src').extract_first()

       # 假设示例
       #content = div.xpath('./div/div//text()').extract() # 提取div下的所有文本内容,是一个列表        #content = "".join(content) # 列表转换成字符串

# 实例化一个FirstbloodItem对象 item = ProxiaohuawangItem() item["name"] = name item["img_src"] = "http://www.xiaohuar.com"+img_src # 每次返回一个item对象提交到pipeline yield item

 

posted @ 2019-08-02 20:46  WiseAdministrator  阅读(501)  评论(0编辑  收藏  举报