爬虫基础以及一个简单的实例

Posted HuZihu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了爬虫基础以及一个简单的实例相关的知识,希望对你有一定的参考价值。

最近在看爬虫方面的知识,看到崔庆才所著的《Python3网络爬虫开发实战》一书讲的比较系统,果断入手学习。下面根据书中的内容,简单总结一下爬虫的基础知识,并且实际练习一下。详细内容请见:https://cuiqingcai.com/5465.html(作者已把书的前几章内容对外公开)。

 

在写爬虫程序之前需要了解的一些知识:

爬虫基础:我们平时访问网页就是对服务器发送请求(Request),然后得到响应(Response)的一个过程。爬虫通过模仿浏览器,对网页进行自动访问。需要知道请求包含哪些内容,请求的方式有哪些,响应包含哪些内容。

网页结构:网页由HTMLCSSJaveScript组成。需要知道其各自的作用是什么,还需要知道到哪个节点去获取自己想要的信息。

其他:了解会话(Session),Cookie,代理(Proxy)的作用。

 

爬虫流程:

  • 爬取网页(获取网页源代码):可使用的库有urllib,requests等;当然,现在很多网页都是动态加载的,对于这些网页,还需使用Selenium等库
  • 解析网页(提取网页中我们需要的信息):定位信息的方式有:正则表达式XPath选择器CSS选择器;可使用的库有re,lxml,Beautiful Soup等
  • 保存结果(将结果保存至文件或数据库):文件有txt,json等格式;数据库可选择mysql,MangoDB等

 

下面让我们来实际操练一下:

 

实例目标:用requests库爬取猫眼电影网上top100的电影(排名,图片,电影名称,上映时间,评分),用正则表达式进行解析,然后将结果保存至txt文件

 

实例网址:https://maoyan.com/board/4

 

首先,导入requests库和re,json模块:

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

 

这样,在main()方法里,我们设定好url,就可以把该网页源代码打印出来:

def main():
    url="https://maoyan.com/board/4"
    html=get_one_page(url)
    print(html)

 

接下来,我们来仔细查看这个源代码,看看怎样用正则表达式把我们需要的信息提取出来。首先用浏览器打开这个网页,然后在浏览器里面选择开发者工具,在Network里查看网页源代码。下面截取一部分:

<div class="content">
    <div class="wrapper">
        <div class="main">
            <p class="update-time">2018-12-30<span class="has-fresh-text">已更新</span></p>
            <p class="board-content">榜单规则:将猫眼电影库中的经典影片,按照评分和评分人数从高到低综合排序取前100名,每天上午10点更新。相关数据来源于“猫眼电影库”。</p>
            <dl class="board-wrapper">
                <dd>
                        <i class="board-index board-index-1">1</i>
    <a href="/films/1203" title="霸王别姬" class="image-link" data-act="boarditem-click" data-val="{movieId:1203}">
      <img src="//ms0.meituan.net/mywww/image/loading_2.e3d934bf.png" alt="" class="poster-default" />
      <img data-src="https://p1.meituan.net/movie/[email protected]_220h_1e_1c" alt="霸王别姬" class="board-img" />
    </a>
    <div class="board-item-main">
      <div class="board-item-content">
              <div class="movie-item-info">
        <p class="name"><a href="/films/1203" title="霸王别姬" data-act="boarditem-click" data-val="{movieId:1203}">霸王别姬</a></p>
        <p class="star">
                主演:张国荣,张丰毅,巩俐
        </p>
<p class="releasetime">上映时间:1993-01-01</p>    </div>
    <div class="movie-item-number score-num">
<p class="score"><i class="integer">9.</i><i class="fraction">6</i></p>   

 

可以看到,电影的排名在一个dd节点下面:

 <dd>
                        <i class="board-index board-index-1">1</i>

因此,相应的正则表达式可以写为:<dd>.*?board-index.*?>(.*?)</i>

 

接下来,我们发现图片在一个a节点下面,但是有两张图片。经过检查,第二个img节点下的data-src属性是图片的链接:

 <img data-src="https://p1.meituan.net/movie/[email protected]_220h_1e_1c" alt="霸王别姬" class="board-img" />

因此,相应的正则表达式可以写为:.*?data-src="(.*?)"    (注:因为这个会接在之前的正则表达式之后,因此最前面写上.*?即可。下同。)

 

再接下来,电影的名称,在一个p节点下面,class为"name":

<p class="name"><a href="/films/1203" title="霸王别姬" data-act="boarditem-click" data-val="{movieId:1203}">霸王别姬</a></p>

相应的正则表达式可以写为:.*?name.*?a.*?>(.*?)</a>

 

上映时间,在一个p节点下面,class为"releasetime":

<p class="releasetime">上映时间:1993-01-01</p>

相应的正则表达式可以写为:.*?releasetime.*?>(.*?)</p>

 

评分,在一个p节点下面,class为"score":

<p class="score"><i class="integer">9.</i><i class="fraction">6</i></p>

相应的正则表达式可以写为:.*?score.*?integer">(.*?)</i>.*?fraction">(.*?)</i>.*?</dd>    (注:最后用dd节点收尾)

 

把这些正则表达式连接起来,然后就可以用findall()方法查找出所有符合条件的内容。完整的正则表达式如下:

<dd>.*?board-index.*?>(.*?)</i>.*?data-src="(.*?)".*?name.*?a.*?>(.*?)</a>.*?releasetime.*?>(.*?)</p>.*?score.*?integer">(.*?)</i>.*?fraction">(.*?)</i>.*?</dd>

 

下面,我们再定义一个解析网页的方法:

def parse_one_page(html):
    pattern=re.compile(<dd>.*?board-index.*?>(.*?)</i>.*?data-src="(.*?)".*?name.*?a.*?>(.*?)</a>.*?releasetime.*?>(.*?)</p>.*?score.*?integer">(.*?)</i>.*?fraction">(.*?)</i>.*?</dd>, re.S)
    result=re.findall(pattern, html)
    return result

这里需要注意,在定义正则表达式的pattern时,必须加上re.S修饰符(匹配包括换行符在内的所有字符),否则碰到换行就无法进行匹配。

 

输出的匹配结果如下:

[(1, https://p1.meituan.net/movie/[email protected]_220h_1e_1c, 霸王别姬, 上映时间:1993-01-01, 9., 6), (2, https://p0.meituan.net/movie/[email protected]_220h_1e_1c, 肖申克的救赎, 上映时间:1994-10-14(美国), 9., 5), (3, https://p0.meituan.net/movie/[email protected]_220h_1e_1c, 罗马假日, 上映时间:1953-09-02(美国), 9., 1), (4, https://p0.meituan.net/movie/[email protected]_220h_1e_1c, 这个杀手不太冷, 上映时间:1994-09-14(法国), 9., 5), (5, https://p1.meituan.net/movie/[email protected]_220h_1e_1c, 教父, 上映时间:1972-03-24(美国), 9., 3), (6, https://p1.meituan.net/movie/[email protected]_220h_1e_1c, 泰坦尼克号, 上映时间:1998-04-03, 9., 5), (7, https://p0.meituan.net/movie/[email protected]_220h_1e_1c, 唐伯虎点秋香, 上映时间:1993-07-01(中国香港), 9., 2), (8, https://p0.meituan.net/movie/[email protected]_220h_1e_1c, 千与千寻, 上映时间:2001-07-20(日本), 9., 3), (9, https://p0.meituan.net/movie/[email protected]_220h_1e_1c, 魂断蓝桥, 上映时间:1940-05-17(美国), 9., 2), (10, https://p0.meituan.net/movie/[email protected]_220h_1e_1c, 乱世佳人, 上映时间:1939-12-15(美国), 9., 1)]

 

可以看出,上述的格式还是有些杂乱,让我们修改一下解析网页的方法,使其变为整齐的结构化数据:

def parse_one_page(html):
    pattern=re.compile(<dd>.*?board-index.*?>(.*?)</i>.*?data-src="(.*?)".*?name.*?a.*?>(.*?)</a>.*?releasetime.*?>(.*?)</p>.*?score.*?integer">(.*?)</i>.*?fraction">(.*?)</i>.*?</dd>, re.S)
    result=re.findall(pattern, html)
    for item in result:
        yield {"index": item[0], "movie_name": item[2],                "pic": item[1], "release": item[3],                "score": item[4]+item[5]}

 

现在匹配结果变成了字典格式:

{index: 1, movie_name: 霸王别姬, pic: https://p1.meituan.net/movie/[email protected]_220h_1e_1c, release: 上映时间:1993-01-01, score: 9.6}
{index: 2, movie_name: 肖申克的救赎, pic: https://p0.meituan.net/movie/[email protected]_220h_1e_1c, release: 上映时间:1994-10-14(美国), score: 9.5}
{index: 3, movie_name: 罗马假日, pic: https://p0.meituan.net/movie/[email protected]_220h_1e_1c, release: 上映时间:1953-09-02(美国), score: 9.1}
{index: 4, movie_name: 这个杀手不太冷, pic: https://p0.meituan.net/movie/[email protected]_220h_1e_1c, release: 上映时间:1994-09-14(法国), score: 9.5}
{index: 5, movie_name: 教父, pic: https://p1.meituan.net/movie/[email protected]_220h_1e_1c, release: 上映时间:1972-03-24(美国), score: 9.3}
{index: 6, movie_name: 泰坦尼克号, pic: https://p1.meituan.net/movie/[email protected]_220h_1e_1c, release: 上映时间:1998-04-03, score: 9.5}
{index: 7, movie_name: 唐伯虎点秋香, pic: https://p0.meituan.net/movie/[email protected]_220h_1e_1c, release: 上映时间:1993-07-01(中国香港), score: 9.2}
{index: 8, movie_name: 千与千寻, pic: https://p0.meituan.net/movie/[email protected]_220h_1e_1c, release: 上映时间:2001-07-20(日本), score: 9.3}
{index: 9, movie_name: 魂断蓝桥, pic: https://p0.meituan.net/movie/[email protected]_220h_1e_1c, release: 上映时间:1940-05-17(美国), score: 9.2}
{index: 10, movie_name: 乱世佳人, pic: https://p0.meituan.net/movie/[email protected]_220h_1e_1c, release: 上映时间:1939-12-15(美国), score: 9.1}

 

接下来要将结果写入txt文件,这里定义一个写入文件的方法:

def write_to_file(result):
    with open ("result.txt","a") as f:
        f.write(json.dumps(result, ensure_ascii=False)+\n)

 

然后在main方法里将结果逐行写入文件:

def main():
    url="https://maoyan.com/board/4"
    html=get_one_page(url)
    result=parse_one_page(html)
    for i in result:
        write_to_file(i)

这里有几个需要注意的地方:1,由于需要将结果逐行写入,因此文件用"a"方式打开,a也就是append。

                                               2,由于需要将结果逐行写入,因此将结果写入文件时最后加上换行符"\n"。

                                               3,由于结果是字典格式,无法直接写入文件,需要先用json.dumps方法把字典转为字符串,但是这样会导致中文乱码。根据json.dumps方法的注释,如果将ensure_ascii设为false,那么写入的字符串可以包含非ASCII字符,否则,所有这些字符都会在JSON字符串中转义。也就是说将参数ensure_ascii设为False可以使中文(UTF-8编码)不经过转义,也就不会乱码。

 

至此,第一页网页就已经全部爬取成功了。但是一共有10页这样的网页,我们打开第二个网页和第三个网页看一下。可以发现,第二个网页的url变为:https://maoyan.com/board/4?offset=10,第三页网页的url则是:https://maoyan.com/board/4?offset=20。可以发现规律就是多了一个offset值,那么我们把1~10页的网页爬取url设置从offset为0,一直到offset为90,就可以爬取所有网页了。

 

由于我们在main方法里设定了爬取url,因此我们给main方法增加一个输入参数,也就是offset偏移值,这样,我们就能爬取我们想要的网页了。最后,再增添一个循环语句,用于爬取各种offset的网页,这样,一个简单的爬虫程序就完成了。

 

我们再把代码重新整合一下,并且由于现在猫眼多了反爬虫,如果爬取速度过快,会没有响应,因此,需要加上一个延时。

 

完整代码如下:

import requests
import re
import json
import time

def get_one_page(url):
    try:
        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
    except requests.RequestException:
        print("Fail")

def parse_one_page(html):
    pattern=re.compile(<dd>.*?board-index.*?>(.*?)</i>.*?data-src="(.*?)".*?name.*?a.*?>(.*?)</a>.*?releasetime.*?>(.*?)</p>.*?score.*?integer">(.*?)</i>.*?fraction">(.*?)</i>.*?</dd>, re.S)
    result=re.findall(pattern, html)
    for item in result:
        yield {"index": item[0], "movie_name": item[2],                "pic": item[1], "release": item[3],                "score": item[4]+item[5]}

def write_to_file(result):
    with open ("result.txt","a") as f:
        f.write(json.dumps(result, ensure_ascii=False)+\n)

def main(offset):
    url="https://maoyan.com/board/4?offset={}".format(offset)
    html=get_one_page(url)
    result=parse_one_page(html)
    for i in result:
        write_to_file(i)

if __name__==__main__:
    for i in range(10):
        main(offset=i*10)
        time.sleep(1)

 

以上是关于爬虫基础以及一个简单的实例的主要内容,如果未能解决你的问题,请参考以下文章

Python基础——爬虫以及简单的数据分析

Java基础之方法的调用重载以及简单的递归

爬虫基础知识与简单爬虫实现

Python 开发简单爬虫 - 基础框架

爬虫,基于request,bs4 的简单实例整合

爬虫简单基础代码