文章目录

  • 正则表达式
  • 正则模块re的使用
  • re模块常量
  • 项目案例
  • XPath解析库
  • 什么是XPath?
  • XPath如何实现文档解析?
  • XPath常用规则
  • 练一练
  • csv读写操作
  • 项目案例



爬虫的四个主要步骤:


实际上爬虫一共就四个主要步骤:

  1. 明确目标 (要知道你准备在哪个范围或者网站去搜索)
  2. 爬 (将所有的网站的内容全部爬下来)
  3. 取 (去掉对我们没用处的数据)
  4. 处理数据(按照我们想要的方式存储和使用)

网路数据解析就是将爬取到的数据进行解析,去除掉没有用的数据。
数据解析的方式主要有三种

  • 1、正则表达式
  • 2、Xpath数据解析库
  • 3、BeautifulSoup数据解析库

正则表达式

正则表达式,又称规则表达式,通常被用来检索、替换那些符合某个模式(规则)的文本。

正则表达式测试工具:

网络爬虫数据分析报告撰写_正则表达式


网络爬虫数据分析报告撰写_网络爬虫数据分析报告撰写_02


网络爬虫数据分析报告撰写_正则表达式_03


网络爬虫数据分析报告撰写_html_04


分组是正则表达式的一项功能,它允许将模式分组在一起,并将它们作为一个项目引用。组是使用括号()创建的。

网络爬虫数据分析报告撰写_html_05

特殊构造:

网络爬虫数据分析报告撰写_html_06

正则模块re的使用

  • 使用步骤
    -1. 使用 compile() 函数将正则表达式的字符串形式编译为一个Pattern对象注意: re对特殊字符进行转义,如果使用原始字符串,只需加一个 r 前缀
    -2. 通过 Pattern 对象对文本进行匹配查找,获得匹配结果,一个 Match 对象。
    -3. 使用 Match 对象提供的属性和方法获得信息,根据需要进行其他的操作

    正则表达式编译成 Pattern 对象, 可以利用 pattern 的一系列方法对文本进行匹配查找了。
import re

text = """
2020-10-10
2020-11-11
2030/12/12
"""
# 1. 使用 compile() 函数将正则表达式的字符串形式编译为一个 Pattern 对象
# 注意: re对特殊字符进行转义,如果使用原始字符串,只需加一个 r 前缀
# pattern = re.compile(r'\d{4}-\d{1,2}-\d{1,2}')    # 2020-4-11, 无分组的规则
# pattern = re.compile(r'(\d{4})-(\d{1,2})-(\d{1,2})')    # 2020-4-11, 有分组的规则
pattern = re.compile(r'(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})')  # 2020-4-11, 有命名分组的规则
#  2. 通过 Pattern 对象对文本进行匹配查找,获得匹配结果,一个 Match 对象。
# search从给定的字符串中寻找一个符合规则的字符串, 只返回一个
result = re.search(pattern, text)
print(result)
# 3. 使用 Match 对象提供的属性和方法获得信息,根据需要进行其他的操作
print("匹配到的信息:", result.group())  # 返回的是匹配到的文本信息
print("匹配到的信息:", result.groups())  # 返回的是位置分组, ('2020', '10', '10')
print("匹配到的信息:", result.groupdict())  # 返回的是关键字分组.{'year': '2020', 'month': '10', 'day': '10'}

正则表达式分割split和替换sub

import  re

# ****************************split***************************
# text = '1+2*4+8-9/10'
# # 字符串方法: '172.25.254.250'.split('.')   => ['172', '25', '254', '250']
# pattern = re.compile(r'\+|-|\*|/')
# # 将字符串根据+或者-或者*或者/进行切割.
# result = re.split(pattern, text)
# print(result)



#***********************sub**************************************
def repl_string(matchObj):
    # matchObj方法: group, groups, groupdict
    print(matchObj)
    items = matchObj.groups()
    print("匹配到的分组内容: ", items)   # ('2019', '10', '10')
    return  "-".join(items)
# 2019/10/10 ====> 2019-10-10
text = "2019/10/10 2020/12/12 2019-12-10  2020-11-10"
pattern = re.compile(r'(\d{4})/(\d{1,2})/(\d{1,2})')  # 注意: 正则规则里面不要随意空格
# 将所有符合条件的信息替换成'2019-10-10'
# result = re.sub(pattern, '2019-10-10', text)
# 将所有符合条件的信息替换成'year-month-day'
result = re.sub(pattern, repl_string, text)
print(result)

re模块常量

常量即表示不可更改的变量,一般用于做标记。下图是常用的四个模块常量:

网络爬虫数据分析报告撰写_正则表达式_07

import re
# *************re.ASSIC******忽略中文********
# text = '正则表达式re模块是python的内置模块'
# pattern = r'\w+'
# result = re.findall(pattern,text,re.A)
# print(result)
# **************re.IGNORECASE****************
# text = 'hello world hello westos HELLO Python'
# pattern = r'he.*?o'
# result = re.findall(pattern,text,re.I)
# print(result)
# **************re.S****************
text = """
hello
world
"""
pattern = r'he.*?ld'
result = re.findall(pattern,text,re.S)
print(result)

Match 对象

  • group([group1, …]) 方法用于获得一个或多个分组匹配的字符串,当要获得整个匹配的子串时,可直接使用 group() 或 group(0);
  • start([group]) 方法用于获取分组匹配的子串在整个字符串中的起始位置(子串第一个字符的索引),参数默认 值为 0;
  • end([group]) 方法用于获取分组匹配的子串在整个字符串中的结束位置(子串最后一个字符的索引+1),参数 默认值为 0;
  • span([group]) 方法返回 (start(group), end(group))。

sub 方法

  • repl 可以是字符串也可以是一个函数:
    1). 如果 repl 是字符串,则会使用 repl 去替换字符串每一个匹配的子串,并返回替换后的字符串,另外,repl 还 可以使用 id 的形式来引用分组,但不能使用编号 0;
    2). 如果 repl 是函数,这个方法应当只接受一个参数(Match 对象),并返回一个字符串用于替换(返回的字符 串中不能再引用分组)。
  • count 用于指定最多替换次数,不指定时全部替换。

匹配中文:
在某些情况下,我们想匹配文本中的汉字,有一点需要注意的是,中文的 unicode 编码范围 主要在 [u4e00- u9fa5],这里说主要是因为这个范围并不完整,比如没有包括全角(中文)标点,不过,在大部分情况下,应该是 够用的。正则表达式测试工具可以从这个网址上查找到一些常用的正则表达式。

# 匹配中文
pattern = r'[\u4e00-\u9fa5]'
text = '正则表达式re模块是python的内置模块'
result = re.findall(pattern,text)
print(result)

贪婪模式与非贪婪模式:abbbc

  1. 贪婪模式:在整个表达式匹配成功的前提下,尽可能多的匹配 ( * );
    使用贪婪的数量词的正则表达式 ab* ,匹配结果: abbb。*决定了尽可能多匹配 b,所以a后面所有的 b 都出现了。
  2. 非贪婪模式:在整个表达式匹配成功的前提下,尽可能少的匹配 ( ? );使用非贪婪的数量词的正则表达式 ab*? ,匹配结果: a。 即使前面有 * ,但是 ? 决定了尽可能少匹配 b,所以没有 b。
  3. Python里数量词默认是贪婪的。

项目案例

基于requests和正则的猫眼电影TOP100定向爬虫
项目介绍: 应用requests库和正则表达式来抓取猫眼电影TOP100的电影名称、时间、评分、图片等信息。
项目分析:

  1. 需求分析,明确采集的网址。
    https://maoyan.com/board 2. 爬, requests数据采集库
    3. 取,正则表达式数据解析库
    4. 存, json格式存储到文件
import json
import re

import requests
from colorama import Fore
from fake_useragent import UserAgent
from requests import HTTPError

def download_page(url,params=None):
    try:
        ua = UserAgent()
        headers = {'User-Agent':ua.random}
        # verify=False 不验证证书
        response = requests.get(url,params = params,headers=headers,verify=False)
    except HTTPError as e:
        print(Fore.RED + '[-]爬取网站%s失败:%s' %(url,str(e)))
        return None
    else:
        return response.text

def parse_html(html):
    """
    通过正则表达式对html解析获取电影名称、时间、评分、图片等信息
    :param html:
    :return:
    """
    pattern = re.compile(
        '<dd>'
        + '.*?<i class="board-index.*?">(\d+)</i>' # 获取电影的排名<i class="board-index board-index-1">1</i>
        + '.*?<img src="(.*?)" alt="(.*?)" class="board-img" />'
        + '.*?<p class="star">(.*?)</p>'  # 获取电影主演信息<p class="star">主演:葛优,巩俐,牛犇</p>
        + '.*?<p class="releasetime">(.*?)</p>' # 获取上映时间<p class="releasetime">上映时间:1994-05-17(法国)</p>
        '.*?</dd>',
        re.S
    )
    # findall返回列表,finditer返回迭代器
    items = re.finditer(pattern,html)
    for item in items:
        # print('*'*10)
        # print(item.groups())
        yield {
            'index': item.groups()[0],
            'image': item.groups()[1],
            'title': item.groups()[2],
            'actors': item.groups()[3].strip().lstrip('主演:'),
            'add_time':item.groups()[4].lstrip('上映时间:')
        }

def save_to_json(data,filename):
    """将爬取的数据信息写入json文件中"""
    import codecs # 可以直接指定文件的编码格式为UTF-8
    # with open(filename,'a') as f:
    #     f.write(json.dumps(data,ensure_ascii=False,indent=4))
    #     print(Fore.GREEN + '[+] 保存电影%s成功' %(data['title']))
    with codecs.open(filename,'a','utf-8') as f:
        f.write(json.dumps(data,ensure_ascii=False,indent=4))

def main():
    url = 'https://maoyan.com/board/4'
    html = download_page(url)
    print(html)
    items = parse_html(html)
    for item in items:
        print(item)
        save_to_json(item,'maoyan.json')

if __name__ == '__main__':
    main()

XPath解析库

什么是XPath?

lxml是python的一个解析库,支持HTML和XML的解析,支持XPath解析方式,而且解析效率非常高。
XPath (XML Path Language) 是一门在 xml文档中查找信息的语言,可用来在 xml /html文档中对元素和属性进行遍历。
查询更多XPath的用法: https://www.w3school.com.cn/xpath/xpath_syntax.asp

XPath如何实现文档解析?

  1. etree库把HTML文档中的字符串解析为Element对象
    from lxml import etree
    html=etree.HTML(text)
    result=etree.tostring(html)
  2. etree库把HTML文档解析为Element对象
    html=etree.parse(‘xxx.html’)
    result=etree.tostring(html,pretty_print=True)
    XPath解析可以对于不符合html语法的代码自动补全,两种方法会一种即可

XPath常用规则

网络爬虫数据分析报告撰写_网络爬虫数据分析报告撰写_08


网络爬虫数据分析报告撰写_正则表达式_09


可以到https://www.w3school.com.cn/xpath/xpath_syntax.asp

查看更多XPath语法

下面是XPath来解析猫眼电影:

def parse_html(html):
    """
    通过正则表达式对html解析获取电影名称、时间、评分、图片等信息
    :param html:
    :return:
    """
# 通过是利用XPath解析页面
    # 1)将传入的html文档内容,通过lxml解析器进行解析
    html = etree.HTML(html)
    # 2)使用XPath语法获取电影信息
    # <dl class="board-wrapper">从根结点找到属性名为board-wrapper的dl标签,拿出里面的dd标签
    movies = html.xpath('//dl[@class="board-wrapper"]/dd')
    for movie in movies:
        # 从当前dd节点寻找i标签,并获取i标签的内容
        index = movie.xpath('./i/text()')[0]
        # print(index)
        image = movie.xpath('.//img[@class="board-img"]/@data-src')[0]    # <img src="https://p0.meituan.net/movie/4c41068ef7608c1d4fbfbe6016e589f7204391.jpg@160w_220h_1e_1c" alt="活着" class="board-img" />
        title = movie.xpath('.//img[@class="board-img"]/@alt')[0]    # <img src="https://p0.meituan.net/movie/4c41068ef7608c1d4fbfbe6016e589f7204391.jpg@160w_220h_1e_1c" alt="活着" class="board-img" />
        actors = movie.xpath('.//p[@class="star"]/text()')[0]    # <p class="star">主演:葛优,巩俐,牛犇</p>
        add_time = movie.xpath('.//p[@class="releasetime"]/text()')[0] # <p class="releasetime">上映时间:1994-05-17(法国)</p>
        yield {
            'index': index,
            'image': image,
            'title': title,
            'actors': actors.strip().lstrip('主演:'),
            'add_time':add_time.lstrip('上映时间:')
        }

练一练

  1. 获取子节点与属性匹配
    //li、 //li/a、//a[@href=“xxx”]/…/@class、//li[@class=“item-1”]
    //li[contains(@class,“aaa”)]/a/text()
  2. 文本获取
    //li[@class=“item-1”]/a/text()、//li[@class=“item-1”]//text()
  3. 属性获取
    //li/a/@href、//li//@href

csv读写操作

逗号分隔值(Comma-Separated Values,CSV)以纯文本形式存储表格数据(数字和文本)。

  • csv文件读取有两种方法: reader()和DictReader()
  • csv文件的写入有两种方法: writer()和DictWriter()
import csv
# with open('hello.csv') as f:
    # for row in f:
    #     print(row)

    # csv_reader = csv.reader(f)
    # for row in csv_reader:
    #     print(row)
    # dict_reader = csv.DictReader(f)
    # for row in dict_reader:
    #     print(row)

# info = [
#     ['fentiao',10],
#     ['fensi',9]
# ]
# with open('writer','w',encoding='utf-8') as f:
#     csv_writer = csv.writer(f)
#     # for row in info:
#     #     csv_writer.writerow(row)
#     csv_writer.writerows(info)

# 通过DictWriter方式写入文件,需要传入一个字典
# with open('writer.csv','w',encoding='utf-8') as f:
#     csv_writer = csv.DictWriter(f,['name','password'])
#     for row in range(10):
#         csv_writer.writerow({
#             'name': 'name' + str(row),
#             'password': 'password' + str(row)
#         })

data = [
    {'name': 'name1','password':'password1'},
    {'name': 'name2','password':'password2'},
    {'name': 'name3','password':'password3'}
]
with open('writer.csv','w',encoding='utf-8') as f:
    csv_writer = csv.DictWriter(f,['name','password'])
    csv_writer.writerows(data)

项目案例

基于requests和Xpath的TIOBE编程语言排行榜定向爬虫
项目介绍: 应用requests库和Xpath来抓取TIOBE编程语言的去年名次、今年名词、编程语言名词、评级和变化率等信息。
项目分析:
1. 需求分析,明确采集的网址。
https://www.tiobe.com/tiobe-index/ 2. 爬, requests数据采集库
3. 取,Xpath数据解析库
4. 存, csv格式存储到文件
网络爬虫数据分析报告撰写_正则表达式_10

import csv
import json
import re
import time

import requests
from colorama import Fore
from fake_useragent import UserAgent
from requests import HTTPError
import lxml
from lxml import etree

def download_page(url,params=None):
    try:
        ua = UserAgent()
        headers = {'User-Agent':ua.random}
        response = requests.get(url,params = params,headers=headers)
    except HTTPError as e:
        print(Fore.RED + '[-]爬取网站%s失败:%s' %(url,str(e)))
        return None
    else:
        return response.text

def parse_html(html):
    # 1).通过lxml解析器解析页面信息,返回Element对象
    html = etree.HTML(html)
    # 2).根据XPath路径寻找语法获取编程语言相关信息
    # 编程语言的去年名次、今年名词、编程语言名词、评级和变化率等信息。
    languages = html.xpath('//table[@id="top20"]/tbody/tr')   # <table id="top20" class="table table-striped table-top20">
    # print(languages[0])
    # 依次获取每种语言的信息
    for language in languages:
        tds = language.xpath('./td')
        list = []
        for td in tds:
            a = td.xpath('./text()')
            list.append(a)
        # print(list)
        # XPath里边的索引从1开始
        # now_rank = language.xpath('./td[1]/text()')
        # last_rank = language.xpath('./td[2]/text()')
        # name = language.xpath('./td[4]/text()')
        # rating = language.xpath('./td[5]/text()')
        # change = language.xpath('./td[6]/text()')
        yield {
            'now_rank' : list[0][0],
            'last_rank': list[1][0],
            'name': list[3][0],
            'rating': list[4][0],
            'change': list[5][0]
        }

def save_to_json(data,filename):
    # 以追加的方式打开文件并写入
    # data是yield返回的字典对象
    # 文件编码格式是utf-8
    # 通过newline设置如何开启新的一行
    with open(filename,'a',encoding='utf-8',newline='') as f:
        csv_writer = csv.DictWriter(f,['now_rank','last_rank','name','rating','change'])
        csv_writer.writeheader()
        for row in data:
            csv_writer.writerow(row)

def get_one_page(page=1):
    url = 'https://www.tiobe.com/tiobe-index/'
    html = download_page(url)
    items = parse_html(html)
    print(items)
    save_to_json(items,'tiobe.csv')
    # for item in items:
    #     save_to_json(item,'tiobe.csv')

if __name__ == '__main__':
    get_one_page()

网络爬虫数据分析报告撰写_html_11