python爬虫入门 之 数据解析


第四章.数据解析

  • 解析 :根据指定的规则对数据进行提取

  • 作用 :实现聚焦爬虫

  • 聚焦爬虫编码流程:

    1.指定url
    2.发起请求
    3.获取响应数据
    4.数据解析
    5.持久化存储

4.1数据解析通用原理

  • 数据解析作用地点

    • 页面源码(一组html标签组成的)

  • html标签核心作用

    • 用于展示数据

  • html是如何展示数据的

    • html所要展示的数据一定是被放置在html标签中,或者是在属性中

  • 通用原理 : 1.标签定位. 2.取文本或取属性

4.2四种数据解析的方式

4.2.1 正则

  • 需求 : 爬取xx百科中糗图数据

    链接地址 : https://www.qiushibaike.com

    两种爬取方式

    #方式一:
    import requests
    #即将发起请求对应的头信息
    headers = {
       "User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36"
    }
    url = "https://pic.qiushibaike.com/system/pictures/12217/122176374/medium/XBOP3Y2YQM1SEEXA.jpg"
    img_data = requests.get(url=url,headers=headers).content    #content返回的是 byte 类型的数据
    with open("./123.jpg",'wb') as f:
       f.write(img_data)
    #方式二:
    from urllib import request
    url = "https://pic.qiushibaike.com/system/pictures/12217/122176374/medium/XBOP3Y2YQM1SEEXA.jpg"
    request.urlretrieve(url,"./456.jpg")
    • 方式一 和 方式二 对于图片操作最大的不同之处是什么?

      • 方式二不可以使用UA伪装的机制

    单页数据的爬取

    #糗事百科
    import re
    import os
    from urllib import request
    ?
    dir_name = "./qiutu"
    if not os.path.exists(dir_name):
       os.mkdir(dir_name)
    ?
    url = "https://www.qiushibaike.com/pic/"
    page_text = requests.get(url=url,headers=headers).text
    #数据解析,图片地址
    #正则表达式
    ex = '
    .*?.*?</div'
    img_src_list = re.findall(ex,page_text,re.S)   # re.S单行匹配
    for src in img_src_list:
       src = "https:" + src
       img_name = src.split("/")[-1]
       #图片存储地址
       img_path = dir_name + "/" + img_name
       #对图片地址单独发起请求获取文件数据
       request.urlretrieve(src,img_path)
       print(img_name,"下载成功")

    爬取分页 分析每个页码对应的url是有共性的:https://www.qiushibaike.com/pic/page/3/?s=5222981

    #糗事百科
    import re
    import os
    from urllib import request
    ?
    dir_name = "./qiutumany"
    if not os.path.exists(dir_name):
       os.mkdir(dir_name)
    ?
    #指定一个通用模板(不可变)
    url = "https://www.qiushibaike.com/pic/page/%d/"
    ?
    for page in range(1,5):
       print("正在打印第{}页的数据".format(page))
       #形成某页码完整url
       new_url = format(url%page)
       page_text = requests.get(url=new_url,headers=headers).text
       #数据解析,图片地址
       #正则表达式
       ex = '
    .*?.*?</div'
       img_src_list = re.findall(ex,page_text,re.S)   # re.S单行匹配
       for src in img_src_list:
           src = "https:" + src
           img_name = src.split("/")[-1]
           #图片存储地址
           img_path = dir_name + "/" + img_name
           #对图片地址单独发起请求获取文件数据
           request.urlretrieve(src,img_path)

模块 :urllib

  • urllib就是一个比较老的网络请求模块,在requests没出现之前,请求发送的都是urllib

4.2.2 bs4

#环境的安装
pip install bs4
pip install lxml
#解析原理
1.实例化一个BeautifulSoup对象,并且将即将被解析的页面源码数据加载到该对象中
2.调用BeautifulSoup对象的相关属性和方法来进行标签定位和数据提取
#如何实例化BeautifulSoup对象
BeautifulSoup(fp,"lxml")    #专门用于解析本地存储的html文档中的数据
BeautifulSoup(page_text,"lxml")  #专门用于将互联网请求到的页面源码数据进行解析

标签定位

soup.tagName   : 定位到第一个tagName标签,返回的是单数
#属性定位:
soup.find(tagName,attrName="value")  返回的是单数
   soup.find_all(tagName,attrName="value")  返回的是列表
#选择器定位:
select("选择器")  返回的是列表
标签,类,i, 层级   > :一个层级  空格:多个层级  
from bs4 import BeautifulSoup
fp = open("./test.html",'r',encoding='utf-8')
soup = BeautifulSoup(fp,"lxml")    #将即将被解析的页面源码加载到对象中
soup.p    #

百里守约


#find用于属性定位
soup.find("div")      
soup.find("div",class_="song")
soup.find_all("div",class_="song")
soup.select(".tang")
soup.select("#feng")
soup.select(".tang > ul > li ")
li_6 = soup.select(".tang > ul > li")[6]    #
  • 度蜜月

  • li_6.i          #度蜜月
    li_6.i.text     #'度蜜月'
    li_6.i.string   #'度蜜月'
    soup.find("a",id="feng")
    soup.find("a",id="feng")["href"]
    • soup.find("div")

     

    • soup.find("div",class_="song")_

       

    • soup.find_all("div",class_="song")

       

    • 类选择器 :soup.select(".tang")

       

    • id选择器 :soup.select("#feng")

       

    • 层级选择器 : soup.select(".tang > ul > li ")

       

    • soup.find("a",id="feng")

    • soup.find("a",id="feng")["href"]

    取数据

    #取文本
    tag.string   标签中直系的文本内容
        tag.text     标签中所有文本内容  
    #取属性
    • 需求:爬取三国演义整篇小说内容

      • 章节名称

      • 章节内容

      • 链接地址 :http://www.shicimingju.com/book/sanguoyanyi.html

      import requests
      #在首页中解析章节名称和每个章节详情页的url
      url = "http://www.shicimingju.com/book/sanguoyanyi.html"
      headers = {
         "User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36"
      }
      page_text = requests.get(url=url,headers=headers).text
      page_text
      soup = BeautifulSoup(page_text,"lxml")
      a_list = soup.select(".book-mulu > ul > li > a")
      fp = open("sanguo.txt",'w',encoding="utf-8")
      for a in a_list:
         detail_url = "http://www.shicimingju.com"+ a["href"]
         title = a.text
         #对章节详情页的url发起请求,解析详情页的章节内容
         detail_page_text = requests.get(url=detail_url,headers=headers).text
         soup = BeautifulSoup(detail_page_text,"lxml")
         chap_content = soup.find("div",class_="chapter_content").text
         fp.write(title + ":"+chap_content)
         print(title,"爬取成功")
      fp.close()

    4.2.3 xpath

    #环境的安装: pip install lxml
    #xpath解析原理:
    1.实例化一个etree类型的对象,且将页面源码加载到该对象中
       2.需要调用该对象的xpath方法结合不同形式的xpath表达式进行标签定位和数据提取
    #etree对象的实例化
    etree.parse(fileName) #用于解析本地存储的html文档中的数据
    etree.HTML(page_text) #用于将互联网请求到的页面源码数据进行解析
    #xpath返回的永远是一个列表

    标签定位

    1.xpath表达式中"最"最左边的"/"表示的含义是,当前定位的标签必须从根节点开始进行定位
    2.xpath表达式中"最"最左边的"//"表示可以从任意位置进行标签定位
    3.xpath表达式中"非"最左边的"/"表示的是一个层级
    4.xpath表达式中"非"最左边的"//"表示的是多个层级
    #属性定位:
    //tageName[@class='value']
    #索引定位:

    提取数据

    #取文本
    /text()   取直系的文本
       //text()   取所有文本内容
    #取属性
    tag/@attrName
    from lxml import etree
    tree = etree.parse("./test.html")
    tree.xpath("/html/head/meta")   #[] -->绝对路径
    tree.xpath("//meta")[0]         #[] -->相对路径,将页面中所有的meta进行定位
    tree.xpath("/html//meta")[0]  
    #属性定位
    tree.xpath('//div[@class="song"]')
    #索引定位
    tree.xpath('//div[@class="tang"]/ul/li[3]')  #该索引从 1 开始
    #取文本
    tree.xpath('//p[1]/text()')   #['百里守约', '李清照']
    tree.xpath('//div[@class="song"]//text()')
    #取属性  
    tree.xpath('//a[@id="feng"]/@href')  #['http://www.haha.com']
    • 需求 :爬取xx直聘招聘信息

      • 岗位名称

      • 公司名称

      • 薪资

      • 岗位描述

      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/76.0.3809.100 Safari/537.36',
         'cookie':'lastCity=101010100; __c=1566877560; __g=-; Hm_lvt_194df3105ad7148dcf2b98a91b5e727a=1566877561; _uab_collina=156687756118178796315757; __l=l=%2Fwww.zhipin.com%2F&r=https%3A%2F%2Fwww.baidu.com%2Flink%3Furl%3DidbSvNzz2fLSl1WXiEmtINauVHUZYSNqejHny725pc5RTwaHqh5uDx1LewpyGmaT%26wd%3D%26eqid%3Dbadf667700040677000000025d64a772&friend_source=0&friend_source=0; __zp_stoken__=91d9QItKEtUk5dMMnDG7lwzq8mBW1g%2FkEsFOHXIi%2FwMd%2FPRRXc%2FPMKjsDYwsfC4b7vAT3FVnTmYBjGp8gW1OeZ5TdA%3D%3D; Hm_lpvt_194df3105ad7148dcf2b98a91b5e727a=1566879753; __a=69160831.1566877560..1566877560.16.1.16.16'
      }
      url = 'https://www.zhipin.com/job_detail/?query=python%E7%88%AC%E8%99%AB&city=101010100&industry=&position='
      page_text = requests.get(url,headers=headers).text
      #数据解析
      tree = etree.HTML(page_text)
      li_list = tree.xpath('//div[@class="job-list"]/ul/li')
      for li in li_list:
      #     需要将li表示的局部页面源码数据中的相关数据进行提取
      #     如果xpath表达式被作用在了循环中,表达式要以./或者.//开头
         detail_url = 'https://www.zhipin.com'+li.xpath('.//div[@class="info-primary"]/h3/a/@href')[0]
         job_title = li.xpath('.//div[@class="info-primary"]/h3/a/div/text()')[0]
         salary = li.xpath('.//div[@class="info-primary"]/h3/a/span/text()')[0]
         company = li.xpath('.//div[@class="info-company"]/div/h3/a/text()')[0]
         #对详情页的url发请求解析出岗位职责
         detail_page_text = requests.get(detail_url,headers=headers).text
         tree = etree.HTML(detail_page_text)
         job_desc = tree.xpath('//div[@class="text"]//text()')
         job_desc = ''.join(job_desc)
         
         print(job_title,salary,company,job_desc)
    • 需求 :爬取xx百科的段子内容和作者名称

      链接地址 :https://www.qiushibaike.com/text/page/3/

      import requests
      from lxml import etree
      ?
      headers = {
         "User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36"
      }
      url = "https://www.qiushibaike.com/text/page/4/"
      page_text = requests.get(url=url,headers=headers).text
      tree = etree.HTML(page_text)
      div_list = tree.xpath("//div[@id='content-left']/div")
      for div in div_list:
         author = div.xpath("./div[1]/a[2]/h2/text() | ./div[1]/span[2]/h2/text()")[0]
         content = div.xpath("./a[1]/div[@class='content']/span//text()")
         content = "".join(content)
         print(author,content)
      #总结:
      另一种较为通用的xpath表达式的使用形式: #管道符"|"在xpath表达式中的应用
         #用处 : 用于解析不规则布局的页面数据

    中文乱码处理问题

    • 爬取http://pic.netbian.com/4kmeishi/的图片和图片名称

    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/76.0.3809.100 Safari/537.36',
    }
    #指定一个通用的url模板
    url = 'http://pic.netbian.com/4kmeishi/index_%d.html'
    ?
    for page in range(1,3):
       if page == 1:
           new_url = 'http://pic.netbian.com/4kmeishi/'
       else:
           new_url = format(url%page)
       response =  requests.get(new_url,headers=headers)
       #response.encoding = 'utf-8'
       page_text = response.text
       tree = etree.HTML(page_text)
       li_list = tree.xpath('//*[@id="main"]/div[3]/ul/li')
       for li in li_list:
           img_src = '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')

    ConnectionPool错误

    #原因一.在短时间内向网站发起了一个高频的请求
    解决方式 :使用代理
    #原因二.连接池(http)中的资源被耗尽
    立即将请求断开 Connection:close

    xpath和bs4最明显的区别

    #解决除携带标签的局部内容
    bs4相关标签定位的方法或属性 返回值就是携带标签的内容

    反爬机制之三 :图片懒加载

    在img标签应用了伪属性

    4.2.4pyquery(扩展)