爬虫教程-基础莫凡

零 概述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ENYJYX6F-1616063089911)(C2CF930A794243A98722AF7E28E0E2B4)]

  • 崔庆才 [的Python爬虫学习系列教程](https://cuiqingcai.com/1052.html/)
  • [知乎](https://www.zhihu.com/question/47883186/)问答中的各种推荐
  • [孔淼](https://www.csdn.net/article/2015-11-13/2826205/) 的一看就明白的爬虫入门讲解
  • 网页解析

    学习爬虫, 首先要懂的是网页. 支撑起各种光鲜亮丽的网页的不是别的, 全都是一些代码. 这种代码我们称之为 HTML, HTML 是一种浏览器(Chrome, Safari, IE, Firefox等)看得懂的语言, 浏览器能将这种语言转换成我们用肉眼看到的网页. 所以 HTML 里面必定存在着很多规律, 我们的爬虫就能按照这样的规律来爬取你需要的信息.

    其实除了 HTML, 一同构建多彩/多功能网页的组件还有 CSS 和 JavaScript. 但是这个简单的爬虫教程, 大部分时间会将会使用 HTML. CSS 和 JavaScript 会在后期简单介绍一下. 因为爬网页的时候多多少少还是要和 CSS JavaScript 打交道的.

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ppp8ZxG6-1616063089912)(2CE257654BE04DC987371E8DC26889C2)]

    虽然莫烦Python主打的是机器学习的教程. 但是这个爬虫教程适用于任何想学爬虫的朋友们. 从机器学习的角度看, 机器学习中的大量数据, 也是可以从这些网页中来, 使用爬虫来爬取各种网页上面的信息, 然后再放入各种机器学习的方法, 这样的应用途径正在越来越多被采用. 所以如果你的数据也是分散在各个网页中, 爬虫是你减少人力劳动的必修课.

    网页基本组成部分

    在真正进入爬虫之前, 我们先来做一下热身运动, 弄明白网页的基础, HTML 有哪些组成部分, 是怎么样运作的. 如果你已经非常熟悉网页的构造了, 欢迎直接跳过这一节, 进入下面的学习.

    我制作了一个非常简易的网页, 给大家呈现以下最骨感的 HTML 结构. 如果你点开它, 呈现在你眼前的, 就是下面这张图的上半部分. 而下半部分就是我们网页背后的 HTML code.

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tF55hXIy-1616063089912)(7389763276A445BD990D9A49F9832728)]

    想问我是如何看到 HTML 的 source code 的? 其实很简单, 在你的浏览器中 (我用的是 Google Chrome), 显示网页的地方, 点击鼠标右键, 大多数浏览器都会有类似这样一个选项 View Page Source. 点击它就能看到页面的源码了.

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k0dWotU7-1616063089913)(88C3AF0DBCA44E9FB5589D7C382C2353)]

    在 HTML 中, 基本上所有的实体内容, 都会有个 tag 来框住它. 而这个被 tag 住的内容, 就是所有HTML的代码,可以被展示成不同的形式, 或有不同的功能. 主体的 tag 分成两部分, header 和 body. 在 header 中, 存放这一些网页的网页的元信息, 比如说 title, 这些信息是不会被显示到你看到的网页中的. 这些信息大多数时候是给浏览器看, 或者是给搜索引擎的爬虫看.

    <head>
        <meta charset="UTF-8">
        <title>Scraping tutorial 1 | 莫烦Python</title>
        <link rel="icon" href="https://mofanpy.com/static/img/description/tab_icon.png">
    </head>
    
     
    

    HTML 的第二大块是 body, 这个部分才是你看到的网页信息. 网页中的 heading, 视频, 图片和文字等都存放在这里. 这里的

    tag 就是主标题, 我们看到呈现出来的效果就是大一号的文字.

    里面的文字就是一个段落. 里面都是一些链接. 所以很多情况, 东西都是放在这些 tag 中的.
    <body>
        <h1>爬虫测试1</h1>
        <p>
            这是一个在 <a href="https://mofanpy.com/">莫烦Python</a>
            <a href="https://mofanpy.com/tutorials/scraping">爬虫教程</a> 中的简单测试.
        </p>
    </body>
    

    爬虫想要做的就是根据这些 tag 来找到合适的信息.

    爬虫时候是爬取body里面的内容,因为我们都想怕我们看到的信息,而body里面写的都是在网页上呈现的元素,但也会爬取head里面的内容

    用 Python 登录网页

    好了, 对网页结构和 HTML 有了一些基本认识以后, 我们就能用 Python 来爬取这个网页的一些基本信息. 首先要做的, 是使用 Python 来登录这个网页, 并打印出这个网页 HTML 的 source code.

    from urllib.request import urlopen
    
    # if has Chinese, apply decode()
    html = urlopen(
        "https://mofanpy.com/static/scraping/basic-structure.html"
    ).read().decode('utf-8')
    print(html)
    
    注意:

    1)首先导入python中urlib库中的urlopen函数可以打开访问一个网页

    url是网页的链接
    

    打开网页之后使用read将网页中的所有内容读取出来
    2)因为网页中存在中文, 为了正常显示中文, read() 完以后, 我们要对读出来的文字进行转换, decode() 成可以正常显示中文的形式.

    运行结果:

    print 出来就是下面这样啦. 这就证明了我们能够成功读取这个网页的所有信息了. 但我们还没有对网页的信息进行汇总和利用. 我们发现, 想要提取一些形式的信息, 合理的利用 tag 的名字十分重要.

    <!DOCTYPE html>
    <html lang="cn">
    <head>
        <meta charset="UTF-8">
        <title>Scraping tutorial 1 | 莫烦Python</title>
        <link rel="icon" href="https://mofanpy.com/static/img/description/tab_icon.png">
    </head>
    <body>
        <h1>爬虫测试1</h1>
        <p>
            这是一个在 <a href="https://mofanpy.com/">莫烦Python</a>
            <a href="https://mofanpy.com/tutorials/scraping">爬虫教程</a> 中的简单测试.
        </p>
    
    </body>
    </html>
    

    匹配网页内容(我们如何在读取网页的所有内容当中寻找筛选到我们所需要的信息)

    匹配信息的工具:

    所以这里我们使用 Python 的正则表达式 RegEx 进行匹配文字, 筛选信息的工作. 我有一个很不错的正则表达式的教程, 如果是初级的网页匹配, 我们使用正则完全就可以了, 高级一点或者比较繁琐的匹配, 我还是推荐使用 BeautifulSoup. 不急不急, 我知道你想偷懒, 我之后马上就会教 beautiful soup 了. 但是现在我们还是使用正则来做几个简单的例子, 让你熟悉一下套路.

    使用原始方法findall选择信息

    如果我们想用代码找到这个网页的 title, 我们就能这样写. 选好要使用的 tag 名称 . 使用正则匹配.

    import re
    res = re.findall(r"<title>(.+?)</title>", html)
    print("\nPage title is: ", res[0])
    
    # Page title is:  Scraping tutorial 1 | 莫烦Python
    

    如果想要找到中间的那个段落

    , 我们使用下面方法, 因为这个段落在 HTML 中还夹杂着 tab, 换行, 所以我们给一个 flags=re.DOTALL 来对这些 tab, new line 不敏感.

    res = re.findall(r"<p>(.*?)</p>", html, flags=re.DOTALL)    # re.DOTALL if multi line
    print("\nPage paragraph is: ", res[0])
    
    
    # Page paragraph is:
    #  这是一个在 <a href="https://mofanpy.com/">莫烦Python</a>
    #  <a href="https://mofanpy.com/tutorials/scraping">爬虫教程</a> 中的简单测试.
    

    最后一个练习是找一找所有的链接, 这个比较有用, 有时候你想找到网页里的链接, 然后下载一些内容到电脑里, 就靠这样的途径了.

    res = re.findall(r'href="(.*?)"', html)
    print("\nAll links: ", res)
    # All links:
    ['https://mofanpy.com/static/img/description/tab_icon.png',
    'https://mofanpy.com/',
    'https://mofanpy.com/tutorials/scraping']
    

    下次我们就来看看为了图方面, 我们如何使用 BeautifulSoup.

    BeautifulSoup 解析网页: 基础

    上节内容, 我们了解了网页 (html) 的基本构架, 知道了爬网页就是在这个构架中找到需要的信息. 那么找到需要的信息时, BeautifulSoup 就是一个找信息好帮手. 它能帮你又快有准地找到信息. 大大简化了使用难度.

    我们总结一下爬网页的流程, 让你对 BeautifulSoup 有一个更好的定位.

    1.选着要爬的网址 (url)

    2.使用 python 登录上这个网址 (urlopen等)

    3.读取网页信息 (read() 出来)

    4.将读取的信息放入 BeautifulSoup

    5.使用 BeautifulSoup 选取 tag 信息等,可以简化用正则表达式匹配的过程 (代替正则表达式匹配html网页中所读取信息的方式)

    初学的时候总是搞不懂这些包是干什么的, 现在你就能理解这个 BeautifulSoup 到底是干什么的了.

    安装

    等什么, 知道 BeautifulSoup 这么方便, 我们就赶紧装一个吧. 安装的步骤很简单, 用 pip 就能轻松安装.

    Python 2+

    pip install beautifulsoup4

    Python 3+

    pip3 install beautifulsoup4

    注意在名字后面还有个 4, 可能是代表第4版吧. 如果你在安装的时候遇到任何问题, 请参考他们官网上的解决方案.

    简单实用方法

    这次我们还是爬一爬上次爬的那个基本网页. BeautifulSoup 使用起来非常简单, 我们先按常规读取网页.

    from bs4 import BeautifulSoup
    from urllib.request import urlopen
    # if has Chinese, apply decode()
    html = urlopen("https://mofanpy.com/static/scraping/basic-structure.html").read().decode('utf-8')
    print(html)
    

    回顾一下, 每张网页中, 都有两大块, 一个是 , 一个是 , 我们等会用 BeautifulSoup 来找到 body 中的段落

    和所有链接 .

    <!DOCTYPE html>
    <html lang="cn">
    <head>
        <meta charset="UTF-8">
        <title>Scraping tutorial 1 | 莫烦Python</title>
        <link rel="icon" href="https://mofanpy.com/static/img/description/tab_icon.png">
    </head>
    <body>
        <h1>爬虫测试1</h1>
        <p>
            这是一个在 <a href="https://mofanpy.com/">莫烦Python</a>
            <a href="https://mofanpy.com/tutorials/scraping">爬虫教程</a> 中的简单测试.
        </p>
    
    </body>
    </html>
    

    读取这个网页信息, 我们将要加载进 BeautifulSoup, 以 lxml 的这种形式加载. 除了 lxml, 其实还有很多形式的解析器(官网有介绍), 不过大家都推荐使用 lxml 的形式.若要使用lxml解析器需要先安装 然后 soup 里面就有着这个 HTML 的所有信息.如果你要输出h1标题, 可以就直接 soup.h1.

    soup = BeautifulSoup(html, features='lxml')
    print(soup.h1)
    

    运行结果:

    <h1>爬虫测试1</h1>
    

    print(’\n’, soup.p)

    <p>
            这是一个在 <a href="https://mofanpy.com/">莫烦Python</a>
    <a href="https://mofanpy.com/tutorials/scraping">爬虫教程</a> 中的简单测试.
        </p>
    

    如果网页中有过个同样的 tag, 比如链接 , 我们可以使用 find_all() 来找到所有的选项. 因为我们真正的 link 不是在 中间 , 而是在 里面, 也可以看做是 的一个属性. 我们能用像 Python 字典的形式, 用 key 来读取 l[“href”].

    <a href="https://mofanpy.com/tutorials/scraping">爬虫教程</a>
    
    
    all_href = soup.find_all('a')
    print(all_href)
    for l in all_href]:
        print(l.["href"])
    print('\n', all_href)
    
    
    

    运行结果:
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5M9ll6na-1616063089913)(7B466D9DC3254EFFA089144FC7FAD594)]

    'https://mofanpy.com/'
    'https://mofanpy.com/tutorials/scraping'
    

    懂得这些还是远远不够的, 真实情况往往比这些复杂. BeautifulSoup 还有很多其他的选择增强器. 下次, 我们来了解一些 CSS 的概念, 用 BeautifulSoup 加上 CSS 来选择内容.

    BeautifulSoup 解析网页: CSS

    爬取target=css标签中的class

    BeautifulSoup 十分好用, 能快速定位到你需要的网页信息. 上次 我们学着使用了 BeautifulSoup, 这次我们将会了解它更强大的功能, 使用 CSS 的 Class 来选择内容.

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2wUhOrVU-1616063089913)(EF4A1D2A6301462FB77375E2796A4941)]

    什么是 CSS

    提到这个, 我们肯定需要知道什么是 CSS. HTML 和 CSS 是一对好搭档, 他们共同组成了当今的众多网页. 如果这个世界上没有 CSS, 你看到的所有网页可能都长得像这样. 特别骨感!

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wF6JGYZ8-1616063089914)(2AFB35C36DA843E1AC70CA1FC1453B5B)]

    如果有 CSS, 你的网页就变得丰富多彩起来. 文字有了颜色, 字体, 位置也多样了, 图片也有规则了.

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AQH09uxM-1616063089914)(D4A2819C69A84531B4B4F5ED5C727FC8)]

    所以, CSS 主要用途就是装饰你 骨感 HTML 页面. 如果将 HTML 比喻成没穿衣服的人, 那 CSS 就是五颜六色的衣服. 穿在人身上让人有了气质. CSS 的规则很多, 好在如果你只是需要爬网页, 你并不需要学习 CSS 的这些用法或规则, (如果你想, 你可以看到这里), 你只需要注意 CSS 的一条规则就能玩转爬虫了.

    CSS 的 Class

    每个css都有class或者id,我们可以通过它来批量获取某种形式或者某种类型的信息,class就是类的意思。这条规则就是 CSS 的 Class, CSS 在装饰每一个网页部件的时候, 都会给它一个名字. 而且一个类型的部件, 名字都可以一样. 比如我们这个练习网页. 里面的字体/背景颜色, 字体大小, 都是由 CSS 来掌控的,相同样式的元素可以用同一种类的css定义,所以我们可以定义该种类再去修饰其他元素.

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ickDmrg8-1616063089914)(0826968FE6B848CD9FE31DFEDFB6EBDC)]

    而 CSS 的代码, 可能就会放在这个网页的 中. 我们先使用 Python 读取这个页面.

    from bs4 import BeautifulSoup
    from urllib.request import urlopen
    
    # if has Chinese, apply decode()
    html = urlopen("https://mofanpy.com/static/scraping/list.html").read().decode('utf-8')
    print(html)
    

    在 中, 你会发现有这样一些东西被放在

     <ul class="jan">
    

    这里, 这个 ul的背景颜色就是黄色的. 而如果是 month 这个类, 它们的字体颜色就是红色.

    <head>
        ...
        <style>
        .jan {
            background-color: yellow;
        }
        ...
        .month {
            color: red;
        }
        </style>
    </head>
    
    <body>
    ...
    <ul>
        <li class="month">一月</li>
        <ul class="jan">
            <li>一月一号</li>
            <li>一月二号</li>
            <li>一月三号</li>
        </ul>
        <li class="feb month">二月</li>
        <li class="month">三月</li>
        <li class="month">四月</li>
        <li class="month">五月</li>
          
    </ul>
    </body>
    

    这样, 我们就知道, 有时候, 网页中, 这种 class 归类一些组件还是很有用的,是一种很好很好的信息捆绑形式, 比如我就想找 jan 下面的这些

     <li>
    

    我就能通过寻找 class=“jan” 找到它们. BeautifulSoup 就能这么干.

    按 Class 匹配

    按 Class 匹配很简单. 比如我要找所有 class=month 的信息. 并打印出它们的 tag 内文字.
    这样我们并想要所有的列表内容,我们只要类是月份所对应的列表内容。

    soup = BeautifulSoup(html, features='lxml')
    
    # use class to narrow search
    month = soup.find_all('li', {"class": "month"})
    for m in month:
        #获得所收集到的列表中所夹的文本信息
        print(m)
        print(m.get_text())
    

    运行结果
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ei9xuGAE-1616063089915)(0DC373E054294A04AFB74092AED64519)]

    一月
    二月
    三月
    四月
    五月
    
    或者找到 class=jan 的信息. 然后在
    <ul> 下面继续找 <ul> 内部的 <li>
    信息. 这样一层层嵌套的信息,
    非常容易找到.
    
    #首先选择出来text包含所有具体日期的list
    jan = soup.find('ul', {"class": 'jan'})
    #jan是一个被在所有内容中选择出的部分内容形成的对象
    #我们对得到的大块内容进一步细化查找所有列表,以及列表中所有的内容
    d_jan = jan.find_all('li')              # use jan as a parent
    for d in d_jan:
        print(d.get_text())
    

    运行结果

    一月一号
    一月二号
    一月三号
    

    如果想要找到一些有着一定格式的信息, 比如使用正则表达来寻找相类似的信息, 比如一些文本信息并不规则使用单独的正则表达式或者bs4无法匹配,我们在 BeautifulSoup 中也能嵌入正则表达式匹配出来, 让 BeautifulSoup 更为强大. 怎么用, 我们就接着往下看啦.

    BeautifulSoup 解析网页: 正则表达

    正则表达式, 是处理文本信息的重要工具, 除了 Python, 在其他的程序语言中, 也有十分重要的地位. 如果将正则表达式 + BeautifulSoup, 岂不是完美中的完美, 哈哈. 我们今天就来看看, 在 BeautifulSoup 中如何使用正则表达式, 获取更有难度的信息.

    正则表达式

    正则表达式很厉害, 它能用简单的规则匹配到多样化的文本信息. 在做爬虫教程之前, 我特地做了一个正则表达式的教程, 为爬虫做铺垫. 所以有兴趣了解使用正则表达式的朋友, 都可以看看这个非常全的正则教程.

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lk63C4ES-1616063089916)(FC19FE2809AA49B4BB629740C1F69A1D)]

    正则匹配

    这次的教程有一些表格形式的 HTML, 在表格中, 有一些信息的格式类似, 我们先用 BeautifulSoup 筛选一些, 然后完全可以用正则给匹配出来. 比如你想下载这个页面的图片, 我们就可以将图片形式的 url 个匹配出来. 之后再下载就简单多了.
    但是有些图片的标签是链接.

    我们先读取这个网页. 导入正则模块 re.

    from bs4 import BeautifulSoup
    from urllib.request import urlopen
    import re
    
    # if has Chinese, apply decode()
    html = urlopen("https://mofanpy.com/static/scraping/table.html").read().decode('utf-8')
    

    我们发现, 如果是图片, 它们都藏在这样一个 tag 中:

    <td>
        <img src="https://mofanpy.com/static/img/course_cover/tf.jpg">
    </td>
    

    所以, 我们可以用 soup 将这些 tag 全部找出来, 但是每一个 img 的链接(src)都可能不同. 或者每一个图片有的可能是 jpg 有的是 png, 如果我们只想挑选 jpg 形式的图片, 我们就可以用这样一个正则 r’.*?.jpg’ 来选取. 把正则的 compile 形式放到 BeautifulSoup 的功能中, 就能选到符合要求的图片链接了.

    soup = BeautifulSoup(html, features='lxml')
    
    #找soup里所有img标签,他的属性用正则来匹配
    img_links = soup.find_all("img", {"src": re.compile('.*?\.jpg')})
    for link in img_links:
        print(link['src'])
    

    运行结果

    https://mofanpy.com/static/img/course_cover/tf.jpg
    https://mofanpy.com/static/img/course_cover/rl.jpg
    https://mofanpy.com/static/img/course_cover/scraping.jpg
    

    又或者我们发现, 我想选一些课程的链接, 而这些链接都有统一的形式, 就是开头都会有 https://morvan., 那我就将这个定为一个正则的规则, 让 BeautifulSoup 帮我找到符合这个规则的链接.

    course_links = soup.find_all('a', {'href': re.compile('https://morvan.*')})
    for link in course_links:
        print(link['href'])
    
    https://mofanpy.com/
    https://mofanpy.com/tutorials/scraping
    https://mofanpy.com/tutorials/machine-learning/tensorflow/
    https://mofanpy.com/tutorials/machine-learning/reinforcement-learning/
    https://mofanpy.com/tutorials/data-manipulation/scraping/
    

    学习了这么多实用的方法, 我们接下来就来做一个小实战, 让我们的爬虫在百度百科上自由爬行, 在各个百科网页上跳来跳去.

    小练习: 爬百度百科

    有了前面几节内容了练习, 我们现在完全有能力爬出你想要的信息了. 看吧, 我说很简单的, 只要你用 Python 打开网页, 用 BeautifulSoup 找准地方, 然后这样循环往复, 就叫做爬虫了. 哈哈. 被我抽象得不行了.

    不过说到底, 爬虫就是打开一个网页,然后从网页中爬取一些信息,然后返回结果.接着再打开一个网页,爬取一些内容,再放回,反复循环而已。

    今天我们就来爬一爬百度百科, 让我们的爬虫从 网络爬虫 这一页开始爬, 然后在页面中寻找其他页面的信息, 然后爬去其他页面, 然后循环这么做, 看看最后我们的爬虫到底爬去了哪.

    百度百科

    百度百科中有很多名词的解释信息, 我们今天从 网页爬虫 的词条开始爬, 然后在页面中任意寻找下一个词条, 爬过去, 再寻找词条, 继续爬. 看看最后我们爬到的词条和 网页爬虫 差别有多大.

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HG8WBQy0-1616063089917)(27ACDACC0A7540AD8F64CB1DD660449F)]

    这个练习看起来挺没意义的, 但是对于了解爬虫, 还是挺有意义的. 用最简单的规律解释了爬虫的真谛.

    观看规律

    这个爬虫说实在的, 并不难, 只有20+行代码. 但是却能让它游走在百度百科的知识的海洋中. 首先我们需要定义一个起始网页, 我选择了 <a href=https://baike.baidu.com/item/网络爬虫/5162711target=’_blank’ >

    网页爬虫. 我们发现, 页面中有一些链接, 指向百度百科中的另外一些词条, 比如说下面这样.

    <a target="_blank" href="/item/%E8%9C%98%E8%9B%9B/8135707" data-lemmaid="8135707">蜘蛛</a>
    <a target="_blank" href="/item/%E8%A0%95%E8%99%AB">蠕虫</a>
    <a target="_blank" href="/item/%E9%80%9A%E7%94%A8%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E"
    

    通用搜索引擎
    通过观察, 我们发现, 链接有些共通之处. 它们都是 /item/ 开头, 夹杂着一些 %E9 这样的东西. 但是仔细搜索一下, 发现还有一些以 /item/ 开头的, 却不是词条. 比如

    史记·2016
    我想, 我们需要对这些链接做一些筛选, 之前提到 的用 BeautifulSoup 和 正则表达式来筛选应该用得上. 有了些思路, 我们开始写代码吧.

    制作爬虫

    导入一些模块, 设置起始页. 并将 /item/… 的网页都放在 his 中, 做一个备案, 记录我们浏览过的网页.刚开始存的"/item/%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB/5162711"代表的就是起始网页网络爬虫的百度百科,而由于链接存在中文所以用%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB乱码表示,在浏览器中直接输入百度百科再加上这段代码,就可以找到爬虫的百度百科。his中存的是我们之前爬的时候搜集的网页,然后再从his中抽出来,进行下一次的爬取。

    from bs4 import BeautifulSoup
    from urllib.request import urlopen
    import re
    #随机挑选网页中的链接打开
    import random
    
    
    base_url = "https://baike.baidu.com"
    his = ["/item/%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB/5162711"]
    

    接着我们先不用循环, 对一个网页进行处理, 走一遍流程, 然后加上循环, 让我们的爬虫能在很多网页中爬取. 下面做的事情, 是为了在屏幕上打印出来我们现在正在哪张网页上, 网页大标题和名字叫什么.

    #所以我们将我们收集的网址中的最后一个网页提出来
    url = base_url + his[-1]
    
    html = urlopen(url).read().decode('utf-8')
    soup = BeautifulSoup(html, features='lxml')
    #find只返回找到匹配的第一个元素
    print(soup.find('h1').get_text(), '    url: ', his[-1])
    # 网络爬虫     url:  /item/%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB/5162711
    

    接下来我们开始在这个网页上找所有target="blank"和href符合要求的 /item/ 网址. 使用一个正则表达式(正则教程) 过滤掉不想要的网址形式. 这样我们找到的网址都是 /item/%xx%xx%xx… 这样的格式了. 之后我们在这些过滤后的网页中随机选一个, 当做下一个要爬的网页. 不过有时候很不幸, 在 sub_urls 中并不能找到合适的网页, 我们就往回跳一个网页, 回到之前的网页中再随机抽一个网页做同样的事.

    # find valid urls
    sub_urls = soup.find_all("a", {"target": "_blank", "href": re.compile("/item/(%.{2})+$")})
    
    if len(sub_urls) != 0:
        his.append(random.sample(sub_urls, 1)[0]['href'])
    else:
        # no valid sub link found
        his.pop()
    print(his)
    
    # ['/item/%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB/5162711', '/item/%E4%B8%8B%E8%BD%BD%E8%80%85']
    

    有了这套体系, 我们就能把它放在一个 for loop 中, 让它在各种不同的网页中跳来跳去.

    his = ["/item/%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB/5162711"]
    
    for i in range(20):
        url = base_url + his[-1]
    
        html = urlopen(url).read().decode('utf-8')
        soup = BeautifulSoup(html, features='lxml')
        print(i, soup.find('h1').get_text(), '    url: ', his[-1])
    
        # find valid urls
        sub_urls = soup.find_all("a", {"target": "_blank", "href": re.compile("/item/(%.{2})+$")})
    
        if len(sub_urls) != 0:
            his.append(random.sample(sub_urls, 1)[0]['href'])
        else:
            # no valid sub link found
            his.pop()
    

    这样我们就能观看我们的爬虫现在爬去了哪? 是不是爬到了和 网页爬虫 起始页完全不相关的地方去了.

    0 网络爬虫     url:  /item/%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB/5162711
    1 路由器     url:  /item/%E8%B7%AF%E7%94%B1%E5%99%A8
    2 服务等级     url:  /item/%E6%9C%8D%E5%8A%A1%E7%AD%89%E7%BA%A7
    ...
    17 呼损率     url:  /item/%E5%91%BC%E6%8D%9F%E7%8E%87
    18 服务等级     url:  /item/%E6%9C%8D%E5%8A%A1%E7%AD%89%E7%BA%A7
    19 呼损率     url:  /item/%E5%91%BC%E6%8D%9F%E7%8E%87
    

    哈哈哈果然! 经过了20个循环, 它爬到了呼损率, 虽然我都不知道这是什么, 看了看这个网页, 又学了新知识了. 这样想起来, 还挺好玩的.
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lbhHkuAN-1616063089917)(61262F3F127241CE969419A3EDEAB820)]

    接下来来的爬虫, 我们将要了解更多需要知道的功能. 用 requests 代替 urlopen, 还有如何从网页下载等.

    多功能的 Requests

    之前 我们通常使用 Python 的自带模块 urllib, 来提交网页请求. 这个模块能满足我们大部分的需求, 但是为了满足你日益膨胀的其他需求, 比如向网页发送信息, 上传图片等等, 我们还有一个伟大的 Python 外部模块 requests, 来有效的处理这些问题.

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Dv7q8FbW-1616063089917)(BA139C39E79A47FDBCB05B41E2B2F3C1)]

    获取网页的方式

    其实在加载网页的时候, 有几种类型, 而这几种类型就是你打开网页的关键. 最重要的类型 (method) 就是 get 和 post (当然还有其他的, 比如 head, delete). 刚接触网页构架的朋友可能又会觉得有点懵逼了. 这些请求的方式到底有什么不同? 他们又有什么作用?

    我们就来说两个重要的, get, post, 95% 的时间, 你都是在使用这两个来请求一个网页.

    post

    账号登录

    搜索内容

    上传图片

    上传文件

    往服务器传数据 等

    get

    正常打开网页

    不往服务器传数据

    这样看来, 很多网页使用 get 就可以了, 比如 莫烦Python 里的所有页面, 都是只是 get 发送请求. 而 post, 我们则是给服务器发送个性化请求, 比如将你的账号密码传给服务器, 让它给你返回一个含有你个人信息的 HTML.

    从主动和被动的角度来说, post 中文是发送, 比较主动, 你控制了服务器返回的内容. 而 get 中文是取得, 是被动的, 你没有发送给服务器个性化的信息, 它不会根据你个性化的信息返回不一样的 HTML.

    post就是你手上有一些个性化的信息,比如搜索关键词,文件,图片等,这些信息是只有你自己拥有的,你自己的信息输入到网页上传到服务器当中,服务器根据你提供的信息和自己本身具有的信息和程序进行运算,然后根据你提供的信息在网页上返回适合你的信息。

    拿登录账号举个例子. 比如我想登录知乎账号(https://www.zhihu.com/people/morvan/activities, 在登录前, 我看到的页面是这样.

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ppWmWQkL-1616063089918)(077659623672407DB3B43F2A6DD424B7)]

    而如果我登录了(登录一般用 post), 我再次登录这个网页 https://www.zhihu.com/people/morvan/activities, 现在显示的 HTML 界面就和我上面那个不一样了, 这就是使用 post 给网页输入你的个性化信息后 (账号密码), 得到的服务器返回的个性化网页. 每个人的账号密码的到的页面都不一样.

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0h0FbG7n-1616063089918)(111DFCBC4B3A40A882C3A890727D3C84)]

    而今天要说的 requests 模块就是干这些的. 它有着各种不同的请求方法, 而且用起来很方便.

    安装 requests

    Requests 是一个 Python 的外部模块, 我们需要手动安装它. 简单的方法, 在你的 terminal 或者是 cmd, 使用 pip 安装就好了.

    python 2+

    pip install requests

    python 3+

    pip3 install requests

    <a href="http://docs.python-requests.org/en/master/user/install/#install" target='_blank'>
    

    官网上还提供了其他途径的安装.

    requests get 请求

    
    <h2 class="tut-h2-pad" id="requests get 请求">requests get 请求
      <a href="https://mofanpy.com/tutorials/data-manipulation/scraping/requests#requests get 请求" class="headerlink" title="Permalink to this headline">¶</a>
    </h2>
    
    

    有了 requests, 我们可以发送个中 method 的请求. 比如 get. 我们想模拟一下百度的搜索.
    首先我们需要观看一下百度搜索的规律. 在百度搜索框中写上 “莫烦python” 我们发现它弹出了一串这么长的网址.

    <a href="https://mofanpy.com/static/results/scraping/3-1-4.png" target="_blank"><img class="course-image lazy-img" src="/static/img/description/loading.gif" data-src="https://mofanpy.com/static/results-small/scraping/3-1-4.png" alt="3-1-4.png" title="3-1-4.png"></a>
    
    

    但是仔细一看, 和 “莫烦Python” 有关的信息, 只有前面一小段 (“s?wd=莫烦python”), 其他的对我们来说都是无用的信息.
    所以我们现在来尝试一下如果把后面的"无用" url 都去掉会怎样? Duang! 我们还是能搜到 “莫烦python”.

    <a href="https://mofanpy.com/static/results/scraping/3-1-5.png" target="_blank"><img class="course-image lazy-img" src="/static/img/description/loading.gif" data-src="https://mofanpy.com/static/results-small/scraping/3-1-5.png" alt="3-1-5.png" title="3-1-5.png"></a>
    

    所以 “s?wd=莫烦python”,我们发现只要s?wd=等号后面的内容不同,我们就可以搜索到不同的内容,这就是我们搜索需要的关键信息. 我们就能用 get 来搭配一些自定义的搜索关键词来用 python 个性化搜索.
    首先, 我们固定不动的网址部分是 “http://www.baidu.com/s”, ? 后面的东西都是一些参数 (parameters), 所以我们将这些 parameters 用 python
    的字典代替, 然后传入 requests.get() 功能. 然后我们还能用 python (webbrowser模块) 打开一个你的默认浏览器, 观看你是否在百度的搜索页面.

    python

    
    import requests
    import webbrowser
    param = {"wd": "莫烦Python"}  # 搜索的信息
    #它会将我们搜索的关键词以及wd:和前面固定格式组成一个url
    r = requests.get('http://www.baidu.com/s', params=param)
    print(r.url)
    #打开链接
    webbrowser.open(r.url)
    

    运行结果

    http://www.baidu.com/s?wd=%E8%8E%AB%E7%83%A6Python
    

    这时, python 会弹出一个浏览器界面, 然后你看到的, 就是 莫烦Python 的搜索结果了.

    requests post 请求

    post 又怎么用呢? 我们举个小例子, 在这个简单网页中, 我们有一个提交信息的窗口, 如果我提交上去这个信息, 那边的服务器会更加这个提交的信息返回出另一个网页. 这就是网页怎么样使用你 post 过去的信息了.

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-edEoIayh-1616063089919)(DA650A64A95F4A979194E90972EA2AA6)]

    比如我在这里填上自己的姓名, 当我点 submit 的时候, 这个姓名(Morvan, Zhou) 就会被提交给服务器, 然后它会根据提交的姓名返回这个网页.

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dvidMYfy-1616063089919)(0624001F50D048D4A0EEC242E9E8D6EA)]

    这样咋看起来好像和上面讲的 get 百度搜索没区别呀? 都是提交一些信息, 返回一个界面. 但是, 重点来了. 你看看网址栏. 你 post 上去的个人信息, 有没有显示在 url 里? 你愿意将你的私密信息显示在 url 里吗? 你 post 过去的信息是交给服务器内部处理的. 不是用来显示在网址上的.

    懂了这些, 我们就来看使用 python 和 requests 怎么做 post 这个操作吧.

    首先我们调出浏览器的 inspect (右键点击 inspect, 中文是检查还是什么来着). 然后发现我们填入姓名的地方原来是在一个 里面.
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4CgBcWo0-1616063089920)(E83825BFE9364BB49CF4654EEB5B2929)]

    这个 里面有一些 个 tag, 我们仔细看到 里面的这个值 name=“firstname” 和 name=“lastname”, 这两个就是我们要 post 提交上去的关键信息了. 我们填好姓名, 为了记录点击 submit 后, 浏览器究竟发生了什么翻天覆地的变化, 我们在 inspect 窗口, 选择 Network, 勾选 Preserve log, 再点击 submit, 你就能看到服务器返回给你定制化后的页面时, 你使用的方法和数据.

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wCyemfQ8-1616063089921)(5BD78440D84742308233E2E9DD09D078)]

    这些数据包括了:

    Request URL (post 要用的 URL)
    Request Method (post)
    Form Data (post 去的信息)
    有了这些记录, 我们就能开始写
    

    Python 来模拟这一次提交 post 了. 根据 ‘firstname’ 和 ‘lastname’, 也就是上图里面的 Form data, 组织成一个 python 字典. 让后把这个字典传入 requests.post(), 注意, 这里的 post 里面的 url, 不是我们填表时的 url (http://pythonscraping.com/pages/files/form.html), 而是要把 Form 信息提交去的那个网页, 也就是上图中查看到的 Request URL (http://pythonscraping.com/files/processing.php).

    #要投入的信息
    data = {'firstname': '莫烦', 'lastname': '周'}
    #要投入的链接requesturl而不是当前网页链接
    r = requests.post('http://pythonscraping.com/files/processing.php', data=data)
    print(r.text)
    

    运行结果

    Hello there, 莫烦 周!

    通过这个练习, 我们对 HTML 中的 Form 有了理解, 学会了怎么样使用 python 来提交 Form, 登录上提交后的页面.

    上传图片

    传照片也是 post 的一种, 我们得将本地的照片文件传送到服务器. 我们使用这个网页来模拟一次传照片的过程.

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QOB22Okb-1616063089921)(7514DB83F48941638F10015673CC277B)]

    如果你留意观察 url, 你会发现, 传送完照片以后的 url 有变动. 我们使用同样的步骤再次检查, 发现, choose file 按键链接的 是一个叫 uploadFile 的名字. 我们将这个名字记下, 放入 python 的字典当一个 key.

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HEuqt1Az-1616063089922)(333A2C28437647D380DEA2E1709750DB)]

    接着在字典中, 使用 open 打开一个图片文件, 当做要上传的文件. 把这个字典放入你的 post 里面的 files 参数. 就能上传你的图片了, 网页会返回一个页面, 将你的图片名显示在上面.

    file = {'uploadFile': open('./image.png', 'rb')}
    r = requests.post('http://pythonscraping.com/files/processing2.php', files=file)
    print(r.text)
    # The file image.png has been uploaded.
    

    登录

    用 post 还有一个重要的, 就是模拟登录. 再登录的时候发生了什么事情呢? 我们使用这个简单的登录网页进行说明.

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3KIc3kV9-1616063089922)(059E87F35C2C466EAD15ACAA5231D11E)]

    通过之前提到的方法, 我们观察一下浏览器给出的记录. 三个重要的方面都被我圈出来了.

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0nlHwyFY-1616063089923)(FC9F6A714BBD457F96E3B0D05A101B69)]

    我们总结一下, 为了这次登录账号, 我们的浏览器做了什么.
    1.使用 post 方法登录了第一个红框的 url

    2.post 的时候, 使用了 Form data 中的用户名和密码

    3.生成了一些 cookies

    第三点我们是从来没有提到过的. cookie, 听起来很熟呀! 每当游览器出现问题的时候, 网上的解决方法是不是都有什么清除 cookie 之类的, 那 cookie 实际上是什么呢? 这里给出了和全面的介绍.

    简单来说, 因为打开网页时, 每一个页面都是不连续的, 没有关联的, cookies 就是用来衔接一个页面和另一个页面的关系. 比如说当我登录以后, 浏览器为了保存我的登录信息, 将这些信息存放在了 cookie 中. 然后我访问第二个页面的时候, 保存的 cookie 被调用, 服务器知道我之前做了什么, 浏览了些什么. 像你在网上看到的广告, 为什么都可能是你感兴趣的商品? 你登录淘宝, 给你推荐的为什么都和你买过的类似? 都是 cookies 的功劳, 让服务器知道你的个性化需求.

    所以大部分时候, 每次你登录, 你就会有一个 cookies, 里面会提到你已经是登录状态了. 所以 cookie 在这时候很重要. cookies 的传递也特别重要, 比如我用 requests.post + payload 的用户信息发给网页, 返回的 r 里面会有生成的 cookies 信息. 接着我请求去登录后的页面时, 使用 request.get, 并将之前的 cookies 传入到 get 请求. 这样就能已登录的名义访问 get 的页面了.

    payload = {'username': 'Morvan', 'password': 'password'}
    r = requests.post('http://pythonscraping.com/pages/cookies/welcome.php', data=payload)
    print(r.cookies.get_dict())
    
    # {'username': 'Morvan', 'loggedin': '1'}
    
    
    r = requests.get('http://pythonscraping.com/pages/cookies/profile.php', cookies=r.cookies)
    print(r.text)
    
    # Hey Morvan! Looks like you're still logged into the site!
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N0jES80n-1616063089923)(2BD315EAC5494E46817B1889F0002651)]

    但是我们没登录完每一个网页都需要手动将cookie传到下一个request中申请登录网页当中

    使用 Session 登录

    不过每次都要传递 cookies 是很麻烦的, 好在 requests 有个很 handy 的功能, 那就是 Session. 在一次会话中, 我们的 cookies 信息都是相连通的, 它自动帮我们传递这些 cookies 信息. 这时我感叹, 程序员真会偷懒~ 哈哈.

    同样是执行上面的登录操作, 下面就是使用 session 的版本. 创建完一个 session 过后, 我们直接只用 session 来 post 和 get. 而且这次 get 的时候, 我们并没有传入 cookies. 但是实际上 session 内部就已经有了之前的 cookies 了.

    session = requests.Session()
    payload = {'username': 'Morvan', 'password': 'password'}
    r = session.post('http://pythonscraping.com/pages/cookies/welcome.php', data=payload)
    print(r.cookies.get_dict())
    
    # {'username': 'Morvan', 'loggedin': '1'}
    
    
    r = session.get("http://pythonscraping.com/pages/cookies/profile.php")
    print(r.text)
    
    # Hey Morvan! Looks like you're still logged into the site!
    

    这就是我们这次的教学, 想了解更多 requests 使用的朋友看到这里. 下一次我们就来看看如何用不同的方式来下载网页内容.

    下载文件

    从网上下载文件的时候你肯定很开心, 比如说什么电影, pdf, 音乐等. 我们使用爬虫, python 一样也可以做到. 而且做得途径还有很多. 今天我们就来探讨有哪些可行的途径, 之后我们还能用这些途径来做一个小实战, 下载国家地理杂志上的美图.

    下载之前
    在下载之前, 我们的弄清楚怎么样下载. 打个比方, 以前有朋友留言说我的教程太多, 不知道从何学起, 我专门制作了一张学习流程图, 这张莫烦Python的个性化学习路线应该也拯救了无数迷途少年吧. 哈哈哈, 今天我们来爬这张图, 还有下载这张图.

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0LvmypxC-1616063089924)(8370007A05664CC59BD0F2CE6906F18F)]

    想下这张图, 我们首先要到这张图所在的网页. 在这个网页中找到这张图的位置, 并右键 inspect, 找到它在 HTML 中的信息.

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FAD7r6u8-1616063089924)(BD343B35A0814071B63AD9B1236C3EEC)]
    发现原图被存放在这个网页, 注意这个地址开头是 /, 并不是完整的网址, 这种形式代表着, 它是在 https://mofanpy.com/ 下面的网址. 所以我们还要将其补全, 才能在网址栏中找到这个图片地址.如果直接是http或者https开头的网址则不需添加任何东西。

    src="/static/img/description/learning_step_flowchart.png"
    

    补全后的网址是:

    https://mofanpy.com/static/img/description/learning_step_flowchart.png
    

    找到了这个网址, 我们就能开始下载了. 为了下载到一个特定的文件夹, 我们先建立一个文件夹吧. 并且规定这个图片下载地址.

    import os
    os.makedirs('./img/', exist_ok=True)
    
    IMAGE_URL = "https://mofanpy.com/static/img/description/learning_step_flowchart.png"
    

    使用 urlretrieve
    在 urllib 模块中, 提供了我们一个下载功能 urlretrieve. 使用起来很简单. 输入下载地址 IMAGE_URL 和要存放的位置. 图片就会被自动下载过去了.

    from urllib.request import urlretrieve
    urlretrieve(IMAGE_URL, './img/image1.png')
    

    使用 request

    而在 requests 模块, 也能拿来下东西. 下面的代码实现了和上面一样的功能, 但是稍微长了点. 但我们为什么要提到 requests 的下载呢? 因为使用它的另一种方法, 我们可以更加有效率的下载大文件.

    import requests
    #将网址内容下载存在r中
    r = requests.get(IMAGE_URL)
    with open('./img/image2.png', 'wb') as f:
    #将r中的内容写入到文件中
        f.write(r.content)
    

    所以说, 如果你要下载的是大文件, 比如视频等. requests 能让你下一点, 保存一点, 而不是要全部下载完才能保存去另外的地方. 这就是一个 chunk 一个 chunk 的下载. 使用 r.iter_content(chunk_size) 来控制每个 chunk 的大小, 然后在文件中写入这个 chunk 大小的数据.

    
    r = requests.get(IMAGE_URL, stream=True)    # stream loading
    
    with open('./img/image3.png', 'wb') as f:
        for chunk in
    
        r.iter_content(chunk_size=32):
            f.write(chunk)
    

    小练习: 下载美图

    有了这些知识的积累, 我们就能开始做一个小的实战练习, 去下载一些国家地理杂志的美图啦.

    学会爬虫, 关键在于练习, 见多识广, 见多了网页的构造, 才知道怎么爬. 今天我们就来一个小实战, 结合之前学习的 requests 访问和 下载功能, 还有 BeautifulSoup, 来下载一些国家地理杂志的美图.

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NiIR2T4w-1616063089924)(127987560B694A24A8E904B17596FE8F)]

    找到图片位置
    说白了, 每次的爬虫, 都是先分析一下这个网页要找的东西的位置, 然后怎么索引上这个位置, 最后用 python 找到它. 这次也是这个逻辑. 我们看看今天要爬的这个图片网址. 定位到最新图片的位置,

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PunVH7ur-1616063089925)(EACA6CBC86A749039D5F173286D4E6AE)]

    找到这张图片的所在位置, 对比这类型的图片, 找到一种手段来筛选这些图片. 发现他们都存在于 img_list 的这种

    • 中.

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AYpDTPlF-1616063089925)(20AF8F12FA3D44579C9983EFFF0E18DD)]

    而图片地址都是在 中.

    <img src="http://image.nationalgeographic.com.cn/2017/1228/20171228030617696.jpg">
    
    #现在我们有了思路, 先找带有
    img_list 的这种 <ul>, 然后在 <ul>
    里面找 <img>.
    
    

    下载图片

    有了思路, 现在我们就用 python 来下图吧. import BeautifulSoup 和 requests. 定义爬取的 url.

    from bs4 import BeautifulSoup
    import requests
    
    URL = "http://www.nationalgeographic.com.cn/animals/"
    

    用 BeautifulSoup 找到带有 img_list 的这种

    • ,

    html = requests.get(URL).text
    soup = BeautifulSoup(html, 'lxml')
    img_ul = soup.find_all('ul', {"class": "img_list"})
    

    从 ul 中找到所有的 , 然后提取 的 src 属性, 里面的就是图片的网址啦. 接着, 就用之前在 requests 下载那节内容里提到的一段段下载.

    for ul in img_ul:
        imgs = ul.find_all('img')
        for img in imgs:
            url = img['src']
            r = requests.get(url, stream=True)
            image_name = url.split('/')[-1]
            with open('./img/%s' % image_name, 'wb') as f:
                for chunk in r.iter_content(chunk_size=128):
                    f.write(chunk)
            print('Saved %s' % image_name)
    
    

    运行结果

    Saved 20171227102206573.jpg
    ...
    Saved 20171214020322682.jpg
    

    我从下载好的照片中抽了一张出来, 哈哈, 是张河马.

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jTgT306n-1616063089926)(08E9ACBFD4F1491390756DB40163F5CF)]

    如果你只是偶尔爬一爬网页, 学到目前为止, 你已经入门了, 但是如果你想要继续深入, 你开始对爬虫的效率担忧, 觉得自己爬得太慢,因为我们只能同时打开一个网页,然后爬取内容在接着打开下一个网页再接着爬取,但是无法同时打开多个网页同时爬取内容,我们将使用多进程实现。 想要大规模爬取网页, 那么接下来的内容就再适合你不过了. 接下来我们就会提到爬虫的提效方法. 而且现在我们爬取的都是静态网页 (莫烦python 就是静态网页), 如果你遇到 JavaScript 很多的动态加载网页 (淘宝等), 后面的 selenium 教程就很适合你.

    加速爬虫: 多进程分布式

    当你看到这里的时候, 说明你已经不满足于自己的爬虫速度, 你想要最求更快, 更便捷的爬虫方法. 你常常会听到用爬虫的人说分布式爬虫. 这就是为了体现便捷和效率而出现的方法. 这一节内容, 我们简单地介绍一下我使用的分布式爬虫方法, 并且用 python 的 multiprocessing 模块编写一个分布式爬虫,同一时刻我们可以不单独只爬取一个网页的内容,而是开启多进程让每一个核多爬取一个网页,这样的我们久咳哟在同一时间爬取多个网页,从而大大提高效率.

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b2drark7-1616063089926)(21F3D0725E69435E929B95CC1B37F90D)]

    什么是分布式爬虫

    分布式爬虫主要是为了非常有效率的抓取网页, 我们的程序一般是单线程跑的, 指令也是一条条处理的, 每执行完一条指令才能跳到下一条. 那么在爬虫的世界里, 这里存在着一个问题.

    如果你已经顺利地执行过了前几节的爬虫代码, 你会发现, 有时候代码运行的时间大部分都花在了下载网页上. 有时候不到一秒能下载好一张网页的 HTML, 有时候却要几十秒. 而且非要等到 HTML 下载好了以后, 才能执行网页分析等步骤. 这非常浪费时间.

    如果我们能合理利用计算资源, 在下载一部分网页的时候就已经开始分析另一部分网页了. 这将会大大节省整个程序的运行时间. 又或者, 我们能同时下载多个网页, 同时分析多个网页, 这样就有种事倍功半的效用. 分布式爬虫的体系有很多种, 处理优化的问题也是多样的. 这里有一篇博客可以当做扩展阅读, 来了解当今比较流行的分布式爬虫框架.

    我们的分布式爬虫

    而今天我们想搭建的这一个爬虫, 就是同时下载, 同时分析的这一种类型的分布式爬虫. 虽然算不上特别优化的框架, 但是概念理解起来比较容易. 我有尝试过徒手写高级一点的分布式爬虫, 但是写起来非常麻烦. 我琢磨了一下, 打算给大家介绍的这种分布式爬虫代码也较好写, 而且效率比普通爬虫快了3.5倍. 我也特地画了张图给大家解释一下要搭建的分布式爬虫.

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B01AcU26-1616063089927)(CB0C1E185CDE45658CE60565D220C950)]

    主要来说, 我们最开始有一个网页, 比如说是莫烦Python的首页, 然后首页中有很多 url, 我们使用多进程 (Python多进程教程) 同时开始下载这些 url, 得到这些 url 的 HTML 以后, 同时开始解析 (比如 BeautifulSoup) 网页内容. 在网页中寻找这个网站还没有爬过的链接然后再放入进程当中. 最终爬完整个 莫烦 Python 网站所有页面.

    有了这种思路, 我们就可以开始写代码了. 你可以在我的 Github 一次性观看全部代码.

    首先 import 全部要用的模块, 并规定一个主页. 注意, 我用这份代码测试我内网的网站(速度不受外网影响) 所以使用的 base_url 是 http://127.0.0.1:4000/, 如果你要爬 莫烦Python, 你的 base_url 要是 https://mofanpy.com/ (下载速度会受外网影响).

    import multiprocessing as mp
    import time
    from urllib.request import urlopen, urljoin
    from bs4 import BeautifulSoup
    import re
    
    # base_url = "http://127.0.0.1:4000/"
    base_url = 'https://mofanpy.com/'
    

    我们定义两个功能, 一个是用来爬取网页的(crawl), 一个是解析网页的(parse). 有了前几节内容的铺垫, 你应该能一言看懂下面的代码. crawl() 用 urlopen 来打开网页, 我用的内网测试, 所以为了体现下载网页的延迟, 添加了一个 time.sleep(0.1) 的下载延迟. 返回原始的 HTML 页面, parse() 就是在这个 HTML 页面中找到需要的信息, 我们用 BeautifulSoup 找 (BeautifulSoup 教程). 返回找到的信息.

    #爬取网页所有内容返回,然后用多进程提取内容再分析
    def crawl(url):
        response = urlopen(url)
        # time.sleep(0.1)             # slightly delay for downloading
        return response.read().decode()
    
    #分析网页内容,找到所有我们需要的信息,我们将所有url,然后爬取网页的url内容和title
    def parse(html):
        soup = BeautifulSoup(html, 'lxml')
        urls = soup.find_all('a', {"href": re.compile('^/.+?/$')})
        title = soup.find('h1').get_text().strip()
        page_urls = set([urljoin(base_url, url['href']) for url in urls])   # 去重
        url = soup.find('meta', {'property': "og:url"})['content']
        return title, page_urls, url
    

    网页中爬取中, 肯定会爬到重复的网址, 为了去除掉这些重复, 我们使用 python 的 set 功能. 定义两个 set, 用来搜集爬过的网页和没爬过的.

    #设立集合存储要访问的网页,保证无重复爬取
    unseen = set([base_url,])
    #保存已将看过的网页
    seen = set()
    
    

    测试普通爬法

    为了对比效果, 我们将在下面对比普通的爬虫和这种分布式的效果. 如果是普通爬虫, 我简化了一下接下来的代码, 将一些不影响的代码去除掉了, 如果你想看全部的代码, 请来到我的 Github. 我们用循环一个个 crawl unseen 里面的 url, 爬出来的 HTML 放到 parse 里面去分析得到结果. 接着就是更新 seen 和 unseen 这两个集合了.

    特别注意: 任何网站都是有一个服务器压力的, 如果你爬的过于频繁, 特别是使用多进程爬取或异步爬取, 一次性提交请求给服务器太多次, 这将可能会使得服务器瘫痪, 你可能再也看不到莫烦 Python 了. 所以为了安全起见, 我限制了爬取数量(restricted_crawl=True). 因为我测试使用的是内网 http://127.0.0.1:4000/ 所以不会有这种压力. 你在以后的爬网页中, 会经常遇到这样的爬取次数的限制 (甚至被封号). 我以前爬 github 时就被限制成一小时只能爬60页.

    # DON'T OVER CRAWL THE WEBSITE OR YOU MAY NEVER VISIT AGAIN
    if base_url != "http://127.0.0.1:4000/":
        restricted_crawl = True
        #限制爬取网站中网页的个数
    else:
        restricted_crawl = False
    
    while len(unseen) != 0:                 # still get some url to visit
        if restricted_crawl and len(seen) >= 20:
            break
    #如果是没爬取过,就爬取一下,并且提取有用的数据
        htmls = [crawl(url) for url in unseen]
        results = [parse(html) for html in htmls]
    #看完以后更新看过和没看过网页的集合
        seen.update(unseen)         # seen the crawled
        unseen.clear()              # nothing unseen
    #将所有招到的结果打印出来
        for title, page_urls, url in results:
            unseen.update(page_urls - seen)     # get new url to crawl
    

    使用这种单线程的方法, 在我的内网上面爬, 爬完整个 莫烦Python, 一共消耗 52.3秒. 接着我们把它改成多进程分布式.

    测试分布式爬法

    还是上一个 while 循环, 首先我们创建一个进程池(Pool). 不太懂进程池的朋友看过来. 然后我们修改得到 htmls 和 results 的两句代码. 其他都不变, 只将这两个功能给并行了. 我在这里写的都是简化代码, 你可以在这里 看到完整代码.

    pool = mp.Pool(4)
    while len(unseen) != 0:
        # htmls = [crawl(url) for url in unseen]
        # --->
        crawl_jobs = [pool.apply_async(crawl, args=(url,)) for url in unseen]
        htmls = [j.get() for j in crawl_jobs]
    
        # results = [parse(html) for html in htmls]
        # --->
        parse_jobs = [pool.apply_async(parse, args=(html,)) for html in htmls]
        results = [j.get() for j in parse_jobs]
    
        ...
    

    还是在内网测试, 只用了 16.3秒!! 这可比上面的单线程爬虫快了3.5倍. 而且我还不是在外网测试的. 如果在外网, 爬取一张网页的时间更长, 使用多进程会更加有效率, 节省的时间更多.

    看到这里, 你一定觉得多线程是爬虫的救星. 其实不然, 要不然我们的教程为什么还能继续. 哈哈. 下一节, 我们会讲到比多进程更加厉害的一种方法,他虽然只用了单线程的功能,但是已经达到了多线程所能达到的效率,叫做异步爬取 (asyncio 模块).

    加速爬虫: 异步加载 Asyncio(Async:异步io:输入输出)

    之前我一直在想如何用 multiprocessing 或者 threading 加速我的爬虫, 也做过了一些小实验, 确实, 我们看到了不小的效率提升. 但是当我更加深入的时候, 我发现, Python 还提供了一个有力的工具, 叫做 asyncio. 这是一个仅仅使用单线程, 就能达到多线程/进程的效果的工具. 它的原理, 简单说就是: 在单线程里使用异步计算, 下载网页的时候和处理网页的时候是不连续的, 更有效利用了等待下载的这段时间.

    传统的单线程下载处理网页可能就像下图(来源)左边蓝色那样, 计算机执行一些代码, 然后等待下载网页, 下好以后, 再执行一些代码… 或者在等待的时候, 用另外一个线程执行其他的代码, 这是多线程的手段. 那么 asyncio 就像右边, 只使用一个线程, 但是将这些等待时间统统掐掉, 下载应该都调到了后台, 这个时间里, 执行其他异步的功能, 下载好了之后, 再调回来接着往下执行.

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bz411Ptu-1616063089927)(75EAF2036EB34D15B29E6D10367AB6AA)]

    如果换一张 Python 自家解释 asyncio 的图(来源), 虽然稍微复杂一点, 但是就是和上图想要表达的是一个意思.

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8YMMMND3-1616063089928)(AE827C67255448D181B340CC8C9109BF)]

    那么, 我们今天就来尝试使用 asyncio 来替换掉 multiprocessing 或者 threading, 看看效果如何.

    Asyncio 库

    Asyncio 库是 Python 的原装库, 但是是在 Python 3 的时候提出来的, Python 2 和 Python 3.3- 是没有的. 而且 Python 3.5 之后, 和 Python 3.4 前在语法上还是有些不同, 比如 await 和 yield 的使用, 下面的教程都是基于 Python 3.5+, 使用 Python3.4 的可能会执行有点问题. 调整一下就好.

    在 3.5+ 版本中, asyncio 有两样语法非常重要, async, await. 弄懂了它们是如何协同工作的, 我们就完全能发挥出这个库的功能了. 剧透一下, 等会使用单线程爬网页的 asyncio 和之前多进程写的爬网页效果差不多, 而且当并行的进程数少的时候, asyncio 效果还会比多进程快.

    基本用法

    接着我们来举例介绍 asyncio, 像之前画的图那样, 我们要时刻记住, asyncio 不是多进程, 也不是多线程, 单单是一个线程, 但是是在 Python 的功能间切换着执行. 切换的点用 await 来标记, 能够异步的功能用 async 标记, 比如 async def function():. 首先我们看一下, 不使用 async 完成的一份代码, 然后我们将这份代码改成 async 版的. 这些代码我都会放在我的 github 中, 如果想一次性看全部, 请来这里.

    # 不是异步的
    import time
    
    
    def job(t):
        print('Start job ', t)
        time.sleep(t)               # wait for "t" seconds
        print('Job ', t, ' takes ', t, ' s')
    
    
    def main():
        [job(t) for t in range(1, 3)]
    
    
    t1 = time.time()
    main()
    print("NO async total time : ", time.time() - t1)
    

    运行结果
    “”"
    Start job 1
    Job 1 takes 1 s
    Start job 2
    Job 2 takes 2 s
    NO async total time : 3.008603096008301
    “”"
    从上面可以看出, 我们的 job 是按顺序执行的, 必须执行完 job 1 才能开始执行 job 2, 而且 job 1 需要1秒的执行时间, 而 job 2 需要2秒. 所以总时间是 3 秒多. 而如果我们使用 asyncio 的形式, job 1 在等待 time.sleep(t) 结束的时候, 比如是等待一个网页的下载成功, 在这个地方是可以切换给 job 2, 让它开始执行.

    import asyncio
    
    
    async def job(t):                   # async 形式的功能
        print('Start job ', t)
        await asyncio.sleep(t)          # 等待 "t" 秒, 期间切换其他任务
        print('Job ', t, ' takes ', t, ' s')
    
    
    async def main(loop):                       # async 形式的功能
        tasks = [
        loop.create_task(job(t)) for t in range(1, 3)
        ]                                       # 创建任务, 但是不执行
        await asyncio.wait(tasks)               # 执行并等待所有任务完成
    
    t1 = time.time()
    loop = asyncio.get_event_loop()             # 建立 loop
    loop.run_until_complete(main(loop))         # 执行 loop,loop等待所有程序结束,也就是所有await返回结果。
    loop.close()                                # 关闭 loop
    print("Async total time : ", time.time() - t1)
    
    

    运行结果
    “”"
    Start job 1
    Start job 2
    Job 1 takes 1 s
    Job 2 takes 2 s
    Async total time : 2.001495838165283
    “”"
    从结果可以看出, 我们没有等待 job 1 的结束才开始 job 2, 而是 job 1 触发了 await 的时候就切换到了 job 2 了. 这时, job 1 和 job 2 同时在等待 await asyncio.sleep(t), 所以最终的程序完成时间, 取决于等待最长的 t, 也就是 2秒. 这和上面用普通形式的代码相比(3秒), 的确快了很多.

    await代表主程序不用管它做的事情,直接做接下来的内容,当前job会留下来在后台运行,而主程序不会等待他结束再接着执行,而主程序会接着执行下一个job

    aiohttp

    有了对 asyncio 的基本了解, 我们就来看怎么把它用在爬虫. 这个功能对于爬虫非常的理想, 原因很简单, 我们在等待一个网页下载的时候, 完全可以切换到其它代码, 事半功倍. 但是 asycio 自己还是没办法完成这项任务的, 我们还需要安装另一个牛逼的模块将 requests 模块代替成一个异步的 requests, 这个牛逼的模块叫作 aiohttp (官网在这). 下载安装特别简单. 直接在你的 terminal 或者 cmd 里面输入 pip3 install aiohttp.

    接着我们来看看我们怎么用最一般的 requests 模块爬网页, 和我们怎么将 requests 替换成 aiohttp.

    
    import requests
    
    URL = 'https://mofanpy.com/'
    
    
    def normal():
        for i in range(2):
            r = requests.get(URL)
            url = r.url
            print(url)
    
    t1 = time.time()
    normal()
    print("Normal total time:", time.time()-t1)
    

    运行结果

    “”"
    https://mofanpy.com/
    https://mofanpy.com/
    Normal total time: 0.3869960308074951
    “”"
    用 requests 用久了以后, 这样的代码真是信手拈来. 很好, 我们打开 莫烦 Python 的首页两次只花了 0.38秒. 然后我们在用 aiohttp 来实现一样的功能. 结果 asyncio 的方式只用了 0.11秒! 大获全胜.

    import aiohttp
    
    
    async def job(session):
        response = await session.get(URL)       # 等待并切换
        return str(response.url)
    
    
    async def main(loop):
        async with aiohttp.ClientSession() as session:      # 官网推荐建立 Session 的形式
            tasks = [loop.create_task(job(session)) for _ in range(2)]
            finished, unfinished = await asyncio.wait(tasks)
            all_results = [r.result() for r in finished]    # 获取所有结果
            print(all_results)
    
    t1 = time.time()
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main(loop))
    loop.close()
    print("Async total time:", time.time() - t1)
    
    """
    ['https://mofanpy.com/', 'https://mofanpy.com/']
    Async total time: 0.11447715759277344
    

    运行结果

    “”"
    我们刚刚创建了一个 Session, 这是官网推荐的方式, 但是我觉得也可以直接用 request 形式, 细节请参考官方说明. 如果要获取网页返回的结果, 我们可以在 job() 中 return 个结果出来, 然后再在 finished, unfinished = await asyncio.wait(tasks) 收集完成的结果, 这里它会返回完成的和没完成的, 我们关心的都是完成的, 而且 await 也确实是等待都完成了才返回. 真正的结果被存放在了 result() 里面.

    和多进程分布式爬虫对比

    有了这些基础, 我们就可以来玩点高级的了, 之前我们用 multiprocessing 写过了一个简单的分布式爬虫, 现在我们就来拿过来 PK 一下 asyncio 的方法. 首先我们对比一下这次写的结构和上次写的简单分布式爬虫的区别. 分布式我们完全依赖的是 multiprocessing 这个模块. 不了解的可以快速过一遍这个教程. 使用 python 强大的并行处理运算来下载我们要处理的 urls, 然后解析网页也是一件耗时的事, 特别是网页量多的时候. 所以我们也将网页解析给并行了. 这样大大节省了下载和运算时间. 再看右边的这个 asyncio 的例子, 我们解析网页还是用的和 multiprocessing 那边一样的并行处理, 因为 asyncio 好像不支持解析网页的异步, 毕竟是计算密集型工序. 然后不一样的地方是, 我们在下载网页时, 不用 multiprocessing, 改用 asyncio, 用一个单线程的东西挑战多进程.

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fJrmPyoc-1616063089928)(A6827D7C3A5942168186228421D528AD)]

    特别注意: 任何网站都是有一个服务器压力的, 如果你爬的过于频繁, 特别是使用多进程爬取或异步爬取, 一次性提交请求给服务器太多次, 这将可能会使得服务器瘫痪, 你可能再也看不到莫烦 Python 了. 所以为了安全起见, 我限制了爬取数量(restricted_crawl=True). 因为我测试使用的是内网 http://127.0.0.1:4000/ 所以不会有这种压力. 你在以后的爬网页中, 会经常遇到这样的爬取次数的限制 (甚至被封号). 我以前爬 github 时就被限制成一小时只能爬60页.

    具体的代码可以在这里详细观看, 需要注意的是, 我使用的内网进行测试(外网的下载速度变动太大), 在下载网页的地方, 我使用 sleep(0.1) 的功能模拟了网页下载的延迟. 一共下载了我 莫烦 Python 的快400个网页. 因为代码表达的内容我已经用上图展示给大家了, 每一个代码都有50-60行, 我就不粘贴在这里了. 具体的结果, 我们可以总结一下.

    Number of ProcessMultiprocessingAsyncio
    225.5s7.5s
    415.4s7.0s
    811.5s7.2s

    我们发现, 如果 Pool(n) 里面的这个 n 越大, 多进程才能越快, 但是 asyncio 却不会特别受进程数的影响. 一个单线程的东西居然战胜了多进程. 可见异步 asyncio 下载网页的重要性.

    上面介绍的还只是 asyncio 的一小部分功能, 如果想了解更多有关于 asyncio 的使用方法, 请看到 Python 的官方介绍.

    高级爬虫: 让 Selenium 控制你的浏览器帮你爬

    Selenium 是为了测试而出生的. 但是没想到到了爬虫的年代, 它摇身一变, 变成了爬虫的好工具. 让我试着用一句话来概括 Seleninm: 它能控制你的浏览器, 有模有样地学人类看网页.他就是基于这个浏览器程序来进行操作的,打开浏览,进行点击,输入密码等操作,最大的优点是可以再浏览器中处理javascript在javascript处理完之后在开始操作我们需要的内容。python内部是通过代码进行操作的,用python脚本运行,拿到的网页返回html是不能处理javascript,javascript有的时候会隐藏某些东西,点击某个按钮才会将隐藏的内容显示返回回来,但是如果你用python脚本是无法点击点操作(即脚本无法处理javascript即网页行为问题)

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K3nxu8KJ-1616063089929)(2A1E10F2D6364E8FA436FACA138E7801)]

    那么你什么时候会要用到 Selenium 呢? 当你: * 发现用普通方法爬不到想要的内容 * 网站跟你玩捉迷藏, 太多 JavaScript 内容 * 需要像人一样浏览的爬虫

    我们本节课实验如何用python程序控制浏览器模拟我们点击网页的过程

    安装 Selenium

    因为 Selenium 需要操控你的浏览器, 所以安装起来比传统的 Python 模块要多几步. 先在 terminal 或者 cmd 用 pip 安装 selenium.

    
    # python 2+
    pip install selenium
    
    # python 3+
    pip3 install selenium
    

    要操控浏览器, 你就要有浏览器的 driver. Selenium 针对几个主流的浏览器都有 driver. 针对 Linux 和 MacOS.

  • Chrome driver, 如果前面链接无法打开, 请尝试这个, 并下载对应版本的 driver
  • Edge driver
  • Firefox driver
  • Safari driver
  • Linux 和 MacOS 用户下载好之后, 请将下载好的geckodriver文件放在你的计算机的 /usr/bin 或 /usr/local/bin 目录. 并赋予执行权限, 不会放的, 请使用这条语句.

    sudo cp 你的geckodriver位置(下载的文件目录) /usr/local/bin
    sudo chmod +x /usr/local/bin/geckodriver
    

    对于 Windows 用户, 官网上的说法, 好像没有提到要具体怎么操作, 我想, 应该是把 geckodriver 这个文件的位置加在 Windows 的环境变量中(PATH).

    如果你安装有任何的问题, 请在它们的官网上查询解决方案.

    偷懒的火狐浏览器插件

    (将点击的东西代码复制出来放到python程序当中
    在这教你用火狐浏览器偷懒的一招, 因为暂时只有火狐上有这个插件. 插件 Katalon Recorder 下载的网址在这

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uhnDinMr-1616063089929)(1295EE00D88B4C4C895D5794D3E9176E)]

    这个插件能让你记录你使用浏览器的操作. 我以前玩网游, 为了偷懒, 用过一个叫按键精灵的东西, 帮我做了很多重复性的工作, 拯救了我的鼠标和键盘, 当然还有我的手指! 看着别人一直在点鼠标, 我心中暗爽~ 这个 Katalon Recorder 插件 + Selenium 就和按键精灵是一个意思. 记录你的操作, 然后你可以让电脑重复上千遍.

    安装好火狐上的这个插件后, 打开它.

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X4r75KVZ-1616063089930)(32FE67582CA146D19B40C7CD0A3DC16A)]

    找到插件上的 record, 点它. 然后用火狐登录上 莫烦Python, 开始你的各种点击工作, 比如我的一连串操作是 (强化学习教程->About页面->赞助页面->教程->数据处理->网页爬虫)

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u0muYwcS-1616063089930)(54A5EEFDA20148B089A5EACB9F899757)]

    每当你点击的时候, 插件就会记录下你这些点击, 形成一些log. 最后神奇的事情将要发生. 你可以点击 Export 按钮, 观看到帮你生成的浏览记录代码!

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RLFna9Ls-1616063089930)(7987C0CF9FA24544B54660E251323308)]

    虽然这个代码输出只有 Python2 版本的, 不过不影响. 我们直接将这些圈起来的代码复制. 这将会是 python 帮你执行的行为代码.

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1WTpieZT-1616063089931)(5954030295654AE982CD7DD00BD32665)]

    Python 控制浏览器

    好了, 有了这些代码, 我们就能回到 Python. 开始写 Python 的代码了. 这里十分简单! 我将 selenium 绑定到 Chrome 上 webdriver.Chrome(). 你可以绑其它的浏览器.

    from selenium import webdriver
    
    driver = webdriver.Chrome()     # 打开 Chrome 浏览器
    
    # 将刚刚复制的帖在这
    driver.get("https://mofanpy.com/")
    driver.find_element_by_xpath(u"//img[@alt='强化学习 (Reinforcement Learning)']").click()
    driver.find_element_by_link_text("About").click()
    driver.find_element_by_link_text(u"赞助").click()
    driver.find_element_by_link_text(u"教程 ▾").click()
    driver.find_element_by_link_text(u"数据处理 ▾").click()
    driver.find_element_by_link_text(u"网页爬虫").click()
    
    # 得到网页 html内容
    html = driver.page_source       # get html
    #,还能网页截图
    driver.get_screenshot_as_file("./img/sreenshot1.png")
    driver.close()
    

    我们能得到页面的 html code (driver.page_source), 就能基于这个 code 来爬取数据了. 最后爬取的网页截图就是这样.

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SOr6MwJY-1616063089931)(B72C8C8F1F59426DA9F441D6E1707A26)]

    不过每次都要看着浏览器执行这些操作, 有时候有点不方便. 我们可以让 selenium 不弹出浏览器窗口, 让它安静地执行操作. 在创建 driver 之前定义几个参数就能摆脱浏览器的身体了.

    from selenium.webdriver.chrome.options import Options
    
    #设置不显示参数
    chrome_options = Options()
    chrome_options.add_argument("--headless")       # define headless
    
    #将参数加入driver中
    driver = webdriver.Chrome(chrome_options=chrome_options)
    ...
    

    最后同样再截一张图, 证明 driver 真的爬到了这个 网页爬虫 教程页面. 不过因为没有出现实体的浏览器, 这个页面大小和上面的图片还是有点差别的.

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V2K3LKCa-1616063089931)(67605A6B58FC466184EC1673CFD910FB)]

    Selenium 能做的事还有很多, 比如填 Form 表单, 超控键盘等等. 这个教程不会细说了, 只是个入门, 如果你还想继续深入了解, 欢迎点进去他们的 Python 教学官网.

    最后, Selenium 的优点我们都看出来了, 可以很方便的帮你模拟你的操作, 添加其它操作也是非常容易的, 但是也是有缺点的, 不是任何时候 selenium 都很好. 因为要打开浏览器, 加载更多东西, 它的执行速度肯定没有其它模块快. 所以如果你需要速度, 能不用 Selenium, 就不用吧.

    高级爬虫: 高效无忧的 Scrapy 爬虫库

    前面的教程我们已经学会了如何写出自己的爬虫, 轻轻松松就能写出一个高性能的爬虫. 如果你想更高效的开发, 爬取网页, 记录数据库, Scrapy 是值得一推的. 它是一个爬虫的框架, 而不是一个简单的爬虫. 它整合了爬取, 处理数据, 存储数据的一条龙服务. 如果你只需要偶尔的一两次爬爬网页, 前面的教程已经够了, 如果你需要每天靠爬虫吃饭, Scrapy 还是有必要了解的.

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bhWB98Tu-1616063089932)(261E6877533D4951BD4A559C5C5E7380)]

    这个教程教你写出一个 Scrapy 形式的爬虫, 带你入门 Scrapy, 但是 Scrapy 不仅仅只有爬虫, 你需要学习更多. 那学习 Scrapy 的地方, 当然是他们自家网站咯.

    Scrapy 的优势

    Scrapy 是一个整合了的爬虫框架, 有着非常健全的管理系统. 而且它也是分布式爬虫, 但是比我们之前写的那个分布式爬虫高级多了. 下面就是 Scrapy 的框架示意图(来源). 它的管理体系非常复杂. 但是特别高效. 让你又刷网页, 又下载, 同时能处理数据. 简直千手观音呀.

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E4wD56NT-1616063089932)(0922129D5B634B928D0B3BC1A514502F)]
    而且做 Scrapy 的项目, 绝对不是只需要写一个脚本就能解决的. 为了把你带入门, 这次我们只写一个脚本, 只涉及里面的爬虫(spider)部分. 其他的部分你可以在这里深入学习.
    schedule是一个控制器控制整个系统组件的工作顺序和协作方式,二download是一个专门处理下载问题的模块,itempiplines是存储我们爬取的内容。

  • 官网教程 英文, 中文
  • JasonDing 的学习Scrapy入门
  • young-hz 的Scrapy研究探索系列
  • scrapy 爬虫

    好了, 我们开始今天的简单 Scrapy 教程吧. 首先你得安装 Scrapy. 在 terminal 或者 cmd 使用 pip 安装就好.

    python 2+

    pip install scrapy

    python 3+

    pip3 install scrapy
    如果安装遇到任何问题, 它们家的网站是个好去处.

    scrapy也是异步爬取的过程但是和我们之前的异步爬取方式不是一个内容。我们之前有做过爬取 莫烦Python 全网的信息. 用多进程 和异步爬取都做过. 这次, 我们就用 Scrapy 来实现这样的一个爬虫. 剧透一下, 做前两个的时候, 代码行数差不多都是 50+ 行, 但是 scrapy 只需要用 20+ 行代码就解决的上面的事. 哈哈, 功能强大吧.

    我们导入 scrapy 模块, 并创建一个 spider 的 class. 并继承 scrapy.Spider, 一定还要给这个 spider 一个名字, 我就用 mofan 好了, 因为是爬 莫烦Python 的. 给定一些初始爬取的网页, 写在 start_urls 里. 这里特别要提的是: 之前我们用 python 的 set 来去除重复的 url, 在 scrapy 中, 这是不需要的, 因为它自动帮你去重. 这可省心多了. 如果你想一次性看到全部代码, 请看到我的 github.

    import scrapy
    
    class MofanSpider(scrapy.Spider):
        name = "mofan"
        start_urls = [
            'https://mofanpy.com/',
        ]
        # unseen = set()
        # seen = set()      # 我们不在需要 set 了, 它自动去重
    接着我们还要定义这个 class 中的一个功能就能完事了. 我们使用 python 的 yield 来返回搜集到的数据 (为什么是yield? 因为在 scrapy 中也有异步处理, 加速整体效率). 这些 title 和 url 的数据, 我们都是用 scrapy 中抓取信息的方式.
    
    class MofanSpider(scrapy.Spider):
        ...
        def parse(self, response):
            yield {     # return some results
                'title': response.css('h1::text').extract_first(default='Missing').strip().replace('"', ""),
                'url': response.url,
            }
    
            urls = response.css('a::attr(href)').re(r'^/.+?/$')     # find all sub urls
            for url in urls:
                yield response.follow(url, callback=self.parse)     # it will filter duplication automatically
    

    然后在这个response网页中筛选 urls, 这里我们也不需要使用 urljoin() 这种功能给 url 改变形式. 它在 follow() 这一步会自动检测 url 的格式. (真是省心啊~), 然后对于每个找到的 url, 然后 yield 重新使用 self.parse() 来爬取, 这里又是自动去重! Scrapy 仿佛知道你最不想做什么, 它自动帮你都做好了. 开心~

    最后需要运行的时候有点不同, 你需要在 terminal 或 cmd 中运行这个爬虫. 而且还能帮你保存刚刚 yield 的 {title:, url:} 的结果. runspider 5-2-scrapy.py 就是选择你要跑的这个 Python 文件.

    $ scrapy runspider 5-2-scrapy.py
    

    -o res.json
    -o res.json 这个 -o 就是输出的指令, 你可以在那个文件夹中找到一个名字叫 res.json 的文件, 里面存有所有找到的 {title:, url:}.

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JLjPBdWX-1616063089932)(FAE58DBFF1B544A29DC188A8BD228225)]

    其实我们今天只做了 scrapy 中的爬虫, 一个正常的 scrapy 项目还包括有很多其他的内容(见下面). 这个教程就不会细说了, 因为学好 scrapy 还是比较麻烦的. 你可以在上面推荐给你的链接中, 继续深入学习 scrapy.

    tutorial/
        scrapy.cfg            # deploy configuration file
    
        tutorial/             # project's Python module, you'll import your code from here
            __init__.py
    
            items.py          # project items definition file
    
            middlewares.py    # project middlewares file
    
            pipelines.py      # project pipelines file
    
            settings.py       # project settings file
    
            spiders/          # a directory where you'll later put your spiders
                __init__.py
    
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python爬虫实例教程-代码,是一个指导如何使用Python编写爬虫教程。下面我将用300字中文来回答这个问题。 这个教程的代码示例主要分为以下几个部分。 第一部分是爬虫的准备工作,包括安装Python和相关的第三方库,如requests、BeautifulSoup等。首先需要安装Python,并配置好相关环境变量。然后使用pip安装requests和BeautifulSoup库,这两个库是编写爬虫时常用的工具。 第二部分是使用requests库发送网络请求,获取网页的HTML源代码。首先需要导入requests库,然后使用get方法发送网络请求,传入要爬取的网页地址。之后可以通过response对象的text属性获取网页的HTML源代码。 第三部分是使用BeautifulSoup库解析HTML源代码,提取出需要的数据。首先需要导入BeautifulSoup库,然后将HTML源代码作为参数传入BeautifulSoup类的构造函数。之后可以使用BeautifulSoup对象的find、findAll等方法,根据HTML标签和属性来提取出需要的数据。 第四部分是保存爬取的数据。可以使用Python自带的文件操作函数,如open、write等,将爬取的数据保存到本地文件中。 最后,还可以通过循环、条件判断等控制结构,以及其他的Python编程技巧,对爬虫代码进行更加复杂的处理和优化。 总的来说,Python爬虫实例教程-代码提供了一个从安装环境到写爬虫代码的全过程。通过学习这些代码示例,可以掌握如何使用Python编写爬虫,以及一些常用的爬虫技巧和工具的使用。希望以上回答可以帮助到你。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值