美文网首页
爬虫笔记(正则与Beautiful Soup对比实现)

爬虫笔记(正则与Beautiful Soup对比实现)

作者: 机智的柠檬 | 来源:发表于2018-10-26 22:22 被阅读0次

    爬虫概述

    通俗的讲,爬虫就是模拟浏览器,向服务器发出请求,获取到服务器返回的内容,再挑出我们想要的内容保存下来。所以,写爬虫主要分为三步:
    1.发出请求
    2.解析页面
    3.保存数据

    一、发出请求

    最基础的HTTP库有urllib,reuests
    首先介绍urllib

    1.1 urllib的使用

    urllib主要有四个模块,request,error,parse,robotparser

    • request : 最基本的HTTP请求模块,可用来模拟发出请。想在浏览器中输入网址,然后回车一样,只需要给库方法传递url以及额外的参数即可。
    • error : 异常处理模块,如果出现请求错误,我们可以捕获这些异常

    urlopen()

    urlopen()参数为url,模拟打开浏览器,下面我们看下使用方法:

    import urllib.request
    response = urllib.request.urlopen("https://www.python.org")
    print(response.read().decode('utf-8'))
    

    返回的是Python 官网的网页源代码:

    <div id="touchnav-wrapper">
    
        <div id="nojs" class="do-not-print">
            <p><strong>Notice:</strong> While Javascript is not essential for this website, your interaction with the content will be limited. Please turn Javascript on for the full experience. </p>
        </div>
    

    接下来,我们看下返回的结果是什么类型:

    print(type(response))
    

    可以看到返回的结果是HTTPResponse 对象:

    <class 'http.client.HTTPResponse'>
    

    所以,response就拥有read() , readinto() , getheader(name) , getheaders() 等方法以及 msg , status , reason , 的属性。

    Request类

    如果需要传入更多的参数,就需要使用Request类
    看下Request类的构造:

    class urllib.request.Request(url,data=None,headers={},origin_req_host=None, unverifiable=False , method= None)
    
    • data : data参数如果要传,需要传入bytes(字节流),如果是字典,可以先用urllib.parse模块里的urlencode()编码
    • headers : headers是一个字典,即请求头。
      下面用一个例子来传入多个参数:
    from urllib import request,parse
    
    url = 'http://httpbin.org/post'
    headers = {
      'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36'
    }
    dict = {
      'name':'Mike'
    }
    data = bytes(parse.urlencode(dict),encoding='utf-8')
    req = request.Request(url=url, data=data, headers=headers, method='POST')
    response = request.urlopen(req)
    print(response.read().decode('utf-8'))
    

    结果为:

    {
      "args": {},
      "data": "",
      "files": {},
      "form": {
        "name": "Mike"
      },
      "headers": {
        "Accept-Encoding": "identity",
        "Connection": "close",
        "Content-Length": "9",
        "Content-Type": "application/x-www-form-urlencoded",
        "Host": "httpbin.org",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36"
      },
      "json": null,
      "origin": "218.94.83.134",
      "url": "http://httpbin.org/post"
    }
    

    1.2 requests的使用

    与urlopen 类似,request.get()方法,也是向浏览器发起请求。

    import requests
    response = requests.get('https://www.python.org')
    print(type(response))
    print(response)
    

    返回结果为:

    <class 'requests.models.Response'>
    <Response [200]>
    

    由此可见,response 是浏览器的Response,可用response.text或者response.content获取网站的内容。前者为str类型,后者为bytes类型。
    同样我们可以传入headers:

    import requests
    headers = {
      'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36'
    }
    response = requests.get('https://www.python.org',headers=headers)
    

    如果请求是post方式,直接传入data即可:

    from urllib import request,parse
    url = 'http://httpbin.org/post'
    data = {
      'name':'Mike'
    }
    data = bytes(parse.urlencode(dict),encoding='utf-8')
    
    response = requests.get(req)
    print(response.text)
    

    注意:两种发起请求方法都可以,两种方法获取信息不一样,urlopen()返回源码使用response.read()方法,requests.get()使用response.text。我一般常用requests,get()方法向浏览器发起请求。

    二、解析页面

    当我们获取到服务器发送回的响应后,我们便通过解析源码来获取到我们想要的内容。解析页面,我们常用的方法有:

    • 正则表达式
    • XPath
    • Beautiful Soup

    2.1 正则表达式

    常用的正则表达式规则:

    模式 描述
    \w 匹配字母、数字、及下划线
    \s 匹配任意空白字符
    \t 匹配一个换行符
    \d 匹配任意数字,等价于[0-9]
    . 匹配任意字符
    * 匹配0个或者多个表达式
    匹配0个或者1个前面的正则表达式定一的片段,非贪婪模式
    a\b (中间是竖线) 匹配a或者b
    () 括号内的表达式,也表示一个组

    常用的方法有:

    • match() : 从字符串的开头开始匹配,(不常用)两个参数,第一个传入正则,第二个传入待匹配字符串,返回结果是SRE_Match对象,有两个属性,group()和span(),前者输出匹配到的内容,后者输出匹配的范围。
    • search() : 匹配时,扫描整个字符串,返回匹配的第一个内容。
    • findall() : 扫描全部字符串,返回所有内容。
    • compile() : 将正则字符串编译成正则对象,方便在后面的匹配中复用。
      注:一般在匹配字符串的时候,总会有换行,所以可以在调用函数的时候,加入re.S参数,可忽略换行。

    下面将用正则表达式为例,爬取起点网玄幻小说的排行榜:
    首先打开起点网,玄幻小说栏目(https://www.qidian.com/rank/click?style=1&page=
    我们将爬取排名,书本连接,书名,作者,还有时间。

    image.png
    打开谷歌开发者环境,找到我们要爬取的目标。
    1、我们先用requests.get()方法,获取目标网页。
    import requests
    def get_one_page(url):
        response = requests.get(url)
        return response.text
    

    函数返回为目标网页的源码。
    2、解析源网页,每一本书都是包含在ul标签下的li标签里,里面包含了我们需要的几个内容。接下来,我们将写出正则表达式匹配到我们想要的内容。


    image.png
    import re
    def parse_one_page(html):
        pattern = re.compile('<li\sdata-rid.*?<span class="rank-tag no.*?">(.*?)<cite>.*?<h4><a\shref="(.*?)".*?>(.*?)</a></h4>',re.S)
        items = re.findall(pattern,html)
        return items
    

    调用该函数,查看返回结果是什么形式的。

    [('1', '//book.qidian.com/info/1010191960', '大王饶命'), ('2', '//book.qidian.com/info/1209977', '斗破苍穹'), ('3', '//book.qidian.com/info/1011705052', '明朝败家子'), ('4', '//book.qidian.com/info/1012486119', '十恶临城'), ('5', '//book.qidian.com/info/1002409852', '诡神冢'), ('6', '//book.qidian.com/info/1011483714', '怪物聊天群'), ('7', '//book.qidian.com/info/1010276884', '狼牙兵王'), ('8', '//book.qidian.com/info/1012237441', '全球高武'), ('9', '//book.qidian.com/info/1011816096', '全职武神'), ('10', '//book.qidian.com/info/1009704712', '牧神记'), ('11', '//book.qidian.com/info/1012749331', '重回80当大佬'), ('12', '//book.qidian.com/info/1010981643', '开天录'), ('13', '//book.qidian.com/info/1004608738', '圣墟'), ('14', '//book.qidian.com/info/3602691', '修真聊天群'), ('15', '//book.qidian.com/info/1011449952', '我在帝都建洞天'), ('16', '//book.qidian.com/info/1010730481', '神级大药师'), ('17', '//book.qidian.com/info/3393401', '极品全能学生'), ('18', '//book.qidian.com/info/1011468740', '我要大宝箱'), ('19', '//book.qidian.com/info/118447', '星辰变'), ('20', '//book.qidian.com/info/1010734492', '凡人修仙之仙界篇')]
    

    可以看到返回结果是列表形式的,里面的元素元组,现在我们已经成功的获取到我们需要的数据了,下面处理并输出到文本里。

    3.保存到文本文件中

    def write_to_txt(content):
      with open('起点小说排行榜.txt','a',encoding='utf-8') as f:
        f.write(content+'\n')
    

    这便是爬取一个页面并保存数据的三个核心步骤,而我们需要前XX名的,翻看第二页的链接,发现只需在后面加上page = i 页即可
    所以可以把我们的url 成'https://www.qidian.com/rank/click?style=1&page='+str(page)即可。

    下面附上完整的代码:

    import requests
    import re
    import json
    url = ''
    def get_one_page(url):
        response = requests.get(url)
        
        return response.text
    
    def parse_one_page(html):
        pattern = re.compile('<li\sdata-rid.*?<span class="rank-tag no.*?">(.*?)<cite>.*?<h4><a\shref="(.*?)".*?>(.*?)</a></h4>',re.S)
        items = re.findall(pattern,html)
        return items
    
    def main(page):
        baseurl = 'https://www.qidian.com/rank/click?style=1&page='
        url = baseurl + str(page)
        
        html = get_one_page(url)
        for item in parse_one_page(html):
            with open('起点小说排行榜.txt','a',encoding='utf-8') as f:
                num = int(item[0]) + page*20 
                f.write(str(num)+"   "+"https:"+item[1]+"   "+item[2]+'\n')
        print('第'+str(page)+'页爬取成功')
    
    if __name__ =='__main__':
        for i in range(26):
            main(i)
    

    至此,我们便可以爬到起点网玄幻小说排名了。

    Beautiful Soup

    相比于正则,Beautiful Soup 选取标签的方法,会使解析页面更加的灵活。共有四种解析方法,我一般用BeautifulSoup(markup,'lxml')来解析。
    基础用法:

    from bs4 import BeautifulSoup 
    soup = BeautifulSoup('<p>Hello</p>','lxml')
    print(soup.p.string)
    

    结果将输出:

    Hello 
    

    Beautiful Soup有三种选择器:

    • 节点选择器 : 如果返回的是单个节点,可以直接string,attrs输出文本和属性,如果是多个节点的生成器,可以转化为列表后取出某个元素,再调用string, attrs输出文本和属性。
    • 方法选择器 : find_all()和fing()。
    • CSS选择器 : 获取文本可以用string ,也可以用get_text()方法。

    下面将用Beautiful Soup 方法解析起点网玄幻小说排行榜:
    抓取页面与正则相同,主要是解析不同,Beautiful Soup的解析代码为:

    def parse_one_page(html):
        soup = BeautifulSoup(html,'lxml')
        contents = []
        for item in soup.select('.rank-view-list li'):
            num = item.select('span')[0].get_text()
            src ='https:' + item.select('a')[0].attrs['href']
            book_name = item.select('h4 a')[0].get_text()
            author = item.select('p a')[0].get_text()
            date = item.select('.update span')[0].get_text()
            content = num + "   " + src + "   " + book_name + "   " + author + "   " + date
            contents.append(content)    
        return contents
    

    将需要的内容存放再一个列表里,打印列表,结果为:

    ['1   https://book.qidian.com/info/1010191960   大王饶命   会说话的肘子   2018-10-26 20:54', '2   https://book.qidian.com/info/1209977   斗破苍穹   天蚕土豆
     2018-09-19 09:59', '3   https://book.qidian.com/info/1011705052   明朝败家子   上山打老虎额   2018-10-26 19:00', '4   https://book.qidian.com/info/1012486119   十恶临城   言桄   2018-10-26 18:31', '5   https://book.qidian.com/info/1002409852   诡神冢   焚天孔雀   2018-10-26 18:31', '6   https://book.qidian.com/info/1011483714   怪物聊天群   泛舟填词   2018-10-26 19:02', '7   https://book.qidian.com/info/1010276884   狼牙兵王   蝼蚁望天   2018-10-26 21:41', '8   https://book.qidian.com/info/1012237441   全球高武   老鹰吃小鸡   2018-10-26 20:27', '9   https://book.qidian.com/info/1011816096   全职武神   流浪的蛤蟆   2018-10-26 10:00', '10   https://book.qidian.com/info/1009704712   牧神记   宅猪   2018-10-26 20:05', '11   https://book.qidian.com/info/1012749331   重回80当大佬   浙东匹夫   2018-10-26 07:49', '12   https://book.qidian.com/info/1010981643   开天录   血红   2018-10-26 12:00', '13   https://book.qidian.com/info/1004608738
     圣墟   辰东   2018-10-25 23:43', '14   https://book.qidian.com/info/3602691   修真聊天群   圣骑士的传说   2018-10-26 00:00', '15   https://book.qidian.com/info/1011449952   我在帝都建洞天   万事皆虚   2018-10-26 18:11', '16   https://book.qidian.com/info/1010730481   神级大药师   微了个信   2018-10-26 21:32', '17
      https://book.qidian.com/info/3393401   极品全能学生   花都大少   2018-10-26 17:00', '18   https://book.qidian.com/info/1011468740   我要大宝箱   风云指上
    2018-10-26 20:00', '19   https://book.qidian.com/info/118447   星辰变   我吃西红柿   2017-09-21 10:23', '20   https://book.qidian.com/info/1010734492   凡人修仙之仙界篇   忘语   2018-10-26 12:30']
    

    返回结果是列表形式的。
    后面同样的保存到本地,再爬取其他页,完整的代码如下:

    import requests
    import pandas
    from bs4 import BeautifulSoup
    
    url = 'https://www.qidian.com/rank/click?style=1&page='
    
    def get_one_page(url):
        response = requests.get(url)
        
        return response.text
    
    def parse_one_page(html):
        soup = BeautifulSoup(html,'lxml')
        contents = []
        for item in soup.select('.rank-view-list li'):
            num = item.select('span')[0].get_text()
            src ='https:' + item.select('a')[0].attrs['href']
            book_name = item.select('h4 a')[0].get_text()
            author = item.select('p a')[0].get_text()
            date = item.select('.update span')[0].get_text()
            content = num + "   " + src + "   " + book_name + "   " + author + "   " + date
            contents.append(content)
        
        return contents
        
    
    def write_to_text(content):
        with open('qidian.txt','a',encoding='utf-8') as w:
            w.write(content + '\n')
           
    
    def main(page):
        url = 'https://www.qidian.com/rank/click?style=1&page=' + str(page)
        html = get_one_page(url)    
        for content in parse_one_page(html):
            write_to_text(content)
    
    if __name__ == '__main__': 
        for page in range(10):
            main(page)
    
    

    总结

    正则解析页面,通用性强,Beautiful Soup则更加简单,更容易理解。

    相关文章

      网友评论

          本文标题:爬虫笔记(正则与Beautiful Soup对比实现)

          本文链接:https://www.haomeiwen.com/subject/ywiltqtx.html