爬虫数据提取:玩转正则表达式(抓取猫眼电影排行)

正则表达式

用来处理字符串--实现字符串的检索、替换、匹配验证 URL: [a-zA-z]+://[^\s]* a-z代表匹配任意小写字母,\s表示匹配任意的空白符号,*代表匹配匹配前面的字符任意多个 正则表达式不是py独有,但py的re库提供了整个正则表达式的实现

  1. match() 向他传入要匹配的字符串以及正则表达式,就可以检测这个正则表达式是否匹配字符串了 从字符串起始位置往后找,匹配则返回成功的结果,否则返回None

    import re
    ​
    content = 'Hello 123 4567 World_This is a Regex Demo'
    print(len(content))
    result = re.match('^Hello\s\d\d\d\s\d{4}\s\w{10}',content)
    print(result) # 匹配成功,返回SRE_Match对象
    print(result.group()) # group()方法可以输出匹配到的内容
    print(result.span()) # span()方法输出匹配范围--(0,25)
    # 开头的^是匹配字符串开头,即以Hello开头,3个\d匹配3个数字,\d{4} 表示匹配4个数字,\w{10}表示匹配10个字母及下划线
    • 匹配目标 这里可以使用()括号将想提取的子字符串括起来。()实际上标记了一个子表达式的开始和结束位置,被标记的每个子表达式会依次对应每一个分组,调用group()方法传人分组的索引即可获取提取的结果

      import re
      content='Hello 1234567 World This is a Regex Demo'
      result = re.match('^Hello\s(\d+)\sWorld', content)
      print(result) # <sre.SRE_Match object; span=(0,19),match='Hello 1234567 World'>
      print(result.group()) # Hello 1234567 World
      print(result.group(1)) # 1234567
      print(result.span()) # (0,19)
      # 若还有括号可用group(2),group(3)...
    • 通用匹配 .(点):可以匹配任意字符(除换行符) (星):代表匹配前面的字符无限次 所以 . 在一起可以匹配任意字符

      import re
      content='Hello 1234567 World This is a Regex Demo'
      result=re.match('^Hello.*Demo$',content)
      print(result)
      print(result.group())
      print(result.span()) # (0,41)
    • 贪婪与非贪婪 我们想要中间的数字

      import re
      content='Hello 1234567 World This is a Regex Demo'
      result=re.match('^Hello.*(\d+).*Demo$',content)
      print(result)
      print(result.group(1)) # 7

      因为.会尽可能多的匹配字符,则\d只要保证至少有一个就好,所以只有7 解决:用.? .*? 是非贪婪,即尽可能少的匹配少的字符,当匹配到Hello后面的空白字符时,再往后的字符就是数字了,而\d+正好可以匹配

      import re
      content='Hello 1234567 World This is a Regex Demo'
      result=re.match('^Hello.*?(\d+).*Demo$',content)
      print(result)
      print(result.group(1)) # 1234567

      如果匹配的结果在字符串结尾,.*?就有可能匹配不到任何内容了,因为它会匹配尽可能少的字符

      import re
      ​
      content='http://weibo.***/***ment/kEra***'
      result1 = re.match('http.*?***ment/(.*?)',content)
      result2 = re.match('http.*?***ment/(.*)',content)
      print('result1',result1.group(1)) # result1
      print('result2',result2.group(1)) # result2 kEra***
    • 修饰符 一个可选的标志

      import re
      ​
      content = '''Hello 1234567 World_This
      is a Regex Demo
      '''
      result = re.match('^He.*?(\d+).*?Demo$',content)
      print(result.group(1)) # 加了换行符匹配不到了,直接None了,调用group()方法导致AttributeError
      # 因为 . 匹配的是除换行符之外的任意字符,当遇到换行,.*?就不匹配了

      加一个re.S的修饰符可以让.匹配包括换行符在内的所有字符

      result = re.match('^He.*?(\d+).*?Demo$',content,re.S) # 1234567
    • 转义匹配 若有些目标字符里就含有.,而正则里的.是匹配除换行符之外的所有字符,则可以用转移匹配实现

      import re
      content = '(百度)www.baidu.***'
      result = re.match('\(百度\)www\.baidu\.***',content)
      print(result) # sre.SRE Match object;span=(0,17),match='(百度)www.baidu.***'>
  2. search() 因为match()是从开头开始匹配的,一旦开头不匹配,则整个匹配失败

    import re
    content ='Extra stings Hello 1234567 World_This is a Regex Demo Extra stings'
    result = re.match('Hello.*?(\d+).*?Demo', content) # 整个正则是字符串中间的一部分,而不是开头开始的一部分
    print(result) # 匹配失败,None

    search()会在匹配时会扫描整个字符串,然后返回第一个成功匹配的结果,否则None 可以匹配一段HTML文本,假设文本中有换行

      result = re.search('<li.*?active.*?singer="(.*?)">(.*?)</a>',html,re.S)
      if result: # 如果匹配成功
          print(result.group(1),result.group(2)) # 齐秦往事随风
  3. findall() search()只返回匹配成功的第一个内容 findall()可以返回所有匹配的内容,如果有返回结果的话,返回的是列表类型,所以需要遍历一下一次获取每组内容

    results = re.findall('<li.*?href="(.*?)".*?singer="(.*?)">(.*?)</a>',html,re.S)
    print(results) # 返回的列表中的每个元素都是元组类型
    print(type(results))
    for result in results:
        print(result)
        print(result[0],result[1],result[2])
  4. sub() 可以借助正则表达式来修改文本 想要把一串文本中的所有数字都去掉,如果只用字符串的replace()方法,那就太烦琐了,这时可以借助sub()方法

    import re
    content = '54aK54yr5oiR54ix5L2g'
    content = re.sub('\d+','',content)
    print(content) # aKyroiRixLg
  5. ***pile() 这个方法可以将正则字符串编译成正则表达式对象,以便在后面的匹配中复用

    import re
    content1 = '2016-12-15 12:00'
    content2 = '2016-12-17 12:55'
    content3 = '2016-12-22 13:21'
    pattern = re.***pile('\d{2}:\d{2}')
    result1 = re.sub(pattern,'',content1) # 将3个日期中的时间去掉
    result2 = re.sub(pattern,'',content2)
    result3 = re.sub(pattern,'',content3)
    print(result1,result2,result3)

    ***pile()还可以传入修饰符re.S等,这样在search()那些方法中就不用一直传了

抓取猫眼电影排行

提取出猫眼电影 TOP100的电影名称、时间、评分、图片等信息,提取的结果会以文件形式保存下来 用requests和正则 URL: http://maoyan.***/board/4 点击第二页时,URL变成了http://maoyan.***/board/4?offset=10,多了一个偏移量参数,因为当前页面排名显示的是11-20名 所以,offset代表偏移量,若偏移量为n,则显示电影序号就是n+1到n+10,每页显示10个 所以,如果想获取TOP100电影,只需要分开请求10次,而10次的offset参数分别设置为0,10,20...90即可

  1. 抓取首页 get_one_page()方法没并且给她出啊如URL地址 然后抓取页面返回页面即可

    import requests
    ​
    def get_one_page(url):
        headers = {
            'User-Agent':'Mozilla/5.0(macintosh;Intel Mac OS X 10_13_3)AppleWebKit/537.36(KHTML,like Gecko)Chrome/65.0.3325.162 Safari/537.36'
        }
        response = requests.get(url,headers=headers)
        if response.status_code == 200:
            return response.text
        return None
    ​
    def main():
        url = 'http://maoyan.***/board/4'
        html = get_one_page(url)
        print(html)
    ​
    main()

    获取到首页的源代码之后,就需要解析页面,提取出我们想要的信息

  2. 正则提取

    import requests
    import re
    def get_one_page(url):
        headers = {
            'User-Agent':'Mozilla/5.0(macintosh;Intel Mac OS X 10_13_3)AppleWebKit/537.36(KHTML,like Gecko)Chrome/65.0.3325.162 Safari/537.36'
        }
        response = requests.get(url,headers=headers)
        if response.status_code == 200:
            return response.text
        return None
    ​
    def parse_one_page(html):
        pattern = re.***pile(
            '<dd>.*?board-index.*?>(.*?)</i>.*?name.*?title="(.*?)".*?star">(.*?)</p>.*?releasetime">(.*?)</p>.*?score.*?>(.*?)</i>.*?fraction">(.*?)</i>.*?</dd>',re.S
        )
        items = re.findall(pattern,html)
    ​
        for item in items: # 可以赋值为一个个的字典,美观
            yield {
                'index':item[0],
                'image':item[1],
                'title':item[2].strip(),
                'actor':item[3].strip()[3:] if len(item[3]) > 3 else '',
                'time':item[4].strip()[5:] if len(item[4]) > 5 else '',
                'score':item[5].strip() + item[6].strip()
            }
    ​
    def main():
        url = "http://maoyan.***/board/4"
        html = get_one_page(url)
        print(html)
        parse_one_page(html)
    ​
    main()
  3. 写入文件 将提取到的结果写入文件,这里直接写入到一个文本文件,通过JSON库的dumps()方法实现字典的序列化,并指定ensure_ascii参数为False,这样可以保证输出结果是中文形式而不是Unicode编码

    def write_to_file(content): # content参数就是一部电影的提取结果,是一个字典
        with open('result.txt','a',encoding='utf-8') as f:
            print(type(json.dumps(content)))
            f.write(json.dumps(content,ensure_ascii=False)+'\n')

    整合

    import requests
    import re
    import json
    ​
    def get_one_page(url):
        headers = {
            'User-Agent':'Mozilla/5.0(macintosh;Intel Mac OS X 10_13_3)AppleWebKit/537.36(KHTML,like Gecko)Chrome/65.0.3325.162 Safari/537.36'
        }
        response = requests.get(url,headers=headers)
        if response.status_code == 200:
            return response.text
        return None
    ​
    def parse_one_page(html):
        pattern = re.***pile(
            '<dd>.*?board-index.*?>(.*?)</i>.*?name.*?title="(.*?)".*?star">(.*?)</p>.*?releasetime">(.*?)</p>.*?score.*?>(.*?)</i>.*?fraction">(.*?)</i>.*?</dd>',re.S
        )
        items = re.findall(pattern,html)
    ​
        for item in items: # 可以赋值为一个个的字典,美观
            yield {
                'index':item[0],
                'image':item[1],
                'title':item[2].strip(),
                'actor':item[3].strip()[3:] if len(item[3]) > 3 else '',
                'time':item[4].strip()[5:] if len(item[4]) > 5 else '',
                'score':item[5].strip() + item[6].strip()
            }
    ​
    def write_to_file(content): # content参数就是一部电影的提取结果,是一个字典
        with open('result.txt','a',encoding='utf-8') as f:
            print(type(json.dumps(content)))
            f.write(json.dumps(content,ensure_ascii=False)+'\n')
    ​
    def main():
        url = "http://maoyan.***/board/4"
        html = get_one_page(url)
        print(html)
        for item in parse_one_page(html):
            write_to_file(item)
    ​
    main()
  4. 分页爬取 遍历一下,给这个链接传人offset 参数,实现其他90部电影的爬取,此时添加如下调用即可:

    if __name__ == '__main__':
        for i in range(10):
            main(offset=i * 10)

    整个代码

    import json
    import requests
    from requests.exceptions import RequestException
    import re
    import time
    ​
    def get_one_page(url):
        try:
            headers = {
            'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3877.400 QQBrowser/10.8.4507.400'
            }
            response = requests.get(url,headers=headers)
            if response.status_code == 200:
                return response.text
            return None
        except RequestException:
            return None
    ​
    def parse_one_page(html):
        pattern = re.***pile(
            '<dd>.*?board-index.*?>(\d+)</i>.*?data-src="(.*?)".*?name"><a.*?>(.*?)</a>.*?star">(.*?)</p>.*?releasetime">(.*?)</p>.*?integer">(.*?)</i>.*?fraction">(.*?)</i>.*?</dd>',re.S
        )
        items = re.findall(pattern,html)
    ​
        for item in items: # 可以赋值为一个个的字典,美观
            yield {
                'index':item[0],
                'image':item[1],
                'title':item[2],
                'actor':item[3].strip()[3:],
                'time':item[4].strip()[5:],
                'score':item[5] + item[6]
            }
    ​
    def write_to_file(content): # content参数就是一部电影的提取结果,是一个字典
        with open('resultt.txt','a',encoding='utf-8') as f: # 以追加模式 ('a') 打开文件,避免覆盖之前的内容,ensure_ascii=False 确保中文字符正常显示。
            f.write(json.dumps(content,ensure_ascii=False)+'\n')
    ​
    def main(offset):
        url = 'http://maoyan.***/board/4?offset=' + str(offset)
        html = get_one_page(url)
        # print(html)
        for item in parse_one_page(html):
            print(item)
            write_to_file(item)
    ​
    if __name__ == '__main__': # 现在猫眼多了反爬虫,如果速度过快,则会无响应,所以这里又增加了一个延时等待
        for i in range(10):
            main(offset=i * 10)
            time.sleep(1)
转载请说明出处内容投诉
CSS教程网 » 爬虫数据提取:玩转正则表达式(抓取猫眼电影排行)

发表评论

欢迎 访客 发表评论

一个令你着迷的主题!

查看演示 官网购买