爬虫初体验
一、什么是爬虫
1、 网络爬虫,简称爬虫,是一种按照一定的规则,自动地抓取互联网信息的程序或者脚本。
2、爬虫做的事情其实和蜘蛛是类似的,所以网络爬虫也被称为网络蜘蛛(spider)。蜘蛛在蜘蛛网上爬来爬去,把触手伸到蜘蛛网获取食物,而网络爬虫则是在互联网上爬来爬去,爬取我们需要的数据。
二、爬虫初体验
1.requests
接下来,我们来学习爬虫中最常用的发起请求的第三方库——requests。下面是 requests 的中文文档页面(https://requests.kennethreitz.org/zh_CN/latest/)。
2.requests.get() 方法
我们从爬虫的第一步 获取数据 开始,我们来看个例子:
import requests # 导入 requests 模块
res = requests.get('https://wpblog.x0y1.com') # 发起请求
print(res)
# 输出:<Response [200]>
我们使用 requests.get(‘网站地址’) 方法向对应的网站发起了请求,然后我们将返回的结果存到了变量 res 中供后续使用。它的类型是 Response 对象,后面的 200 是状态码,我们后面再细说。
这样我们就发起了一次请求,并将服务器的响应结果存到了变量当中,requests.get() 方法的作用如下图所示:
该处使用的url网络请求的数据。
三、Response 对象
我们前面通过 requests.get() 方法获取到了网页的数据,作为 Response 对象存到了变量 res,那么我们如何查看它的具体内容呢?
接下来我们来看看 Response 对象的常用属性:
属性 | 含义 |
---|---|
res.status_code | 响应的HTTP状态码 |
res.text | 响应内容的字符串形式 |
res.content | 响应内容的二进制形式 |
res.encoding | 响应内容的编码 |
1.res.status_code
import requests
res = requests.get('https://wpblog.x0y1.com')
print(res.status_code)
# 输出:200
这里的 200 就是响应的状态码,表示请求成功。当请求失败时会有不同的状态码,不同的状态码有不同的含义,常见的状态码如下:
响应状态码 | 含义 | 例子 | 含义 |
---|---|---|---|
1xx | 消息 | 100 | 继续发出请求 |
2xx | 请求成功 | 200 | 请求成功 |
3xx | 重定向 | 301 | 永久重定向 |
4xx | 客户端错误 | 404 | 找不到资源 |
5xx | 服务端错误 | 503 | 服务不可用 |
例: |
import requests
res = requests.get('https://wpblog.x0y1.com')
# 补全下行代码
if res.status_code==200:
print('请求成功')
else:
print('请求失败')
2.res.text
res.text 返回的是服务器响应内容的字符串形式,也就是文本内容。我们直接看段代码
import requests
res = requests.get('https://wpblog.x0y1.com')
print(res.text)
上面代码的运行结果(结果太长省略了一部分)是:
<!DOCTYPE html>
<html lang="zh-CN" class="no-js no-svg">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script>(function(html){html.className = html.className.replace(/\bno-js\b/,'js')})(document.documentElement);</script>
<title>扇贝编程 – 爬虫示例站点</title>
<link rel="alternate" type="application/rss+xml" title="扇贝编程 » Feed" href="https://wpblog.x0y1.com/?feed=rss2" />
<link rel="alternate" type="application/rss+xml" title="扇贝编程 » 评论Feed" href="https://wpblog.x0y1.com/?feed=comments-rss2" />
<script type="text/javascript">
window._wpemojiSettings = {"baseUrl":"https:\/\/s.w.org\/images\/core\/emoji\/12.0.0-1\/72x72\/","ext":".png","svgUrl":"https:\/\/s.w.org\/images\/core\/emoji\/12.0.0-1\/svg\/","svgExt":".svg","source":{"concatemoji":"https:\/\/wpblog.x0y1.com\/wp-includes\/js\/wp-emoji-release.min.js?ver=5.2.3"}};
!function(a,b,c){function d(a,b){var c=String.fromCharCode;l.clearRect(0,0,k.width,k.height),l.fillText(c.apply(this,a),0,0);var d=k.toDataURL();l.clearRect(0,0,k.width,k.height),l.fillText(c.apply(this,b),0,0);var e=k.toDataURL();return d===e}function e(a){var b;if(!l||!l.fillText)return!1;switch(l.textBaseline="top",l.font="600 32px Arial",a){case"flag":return!(b=d([55356,56826,55356,56819],[55356,56826,8203,55356,56819]))&&(b=d([55356,57332,56128,56423,56128,56418,56128,56421,56128,56430,56128,56423,56128,56447],[55356,57332,8203,56128,56423,8203,56128,56418,8203,56128,56421,8203,56128,56430,8203,56128,56423,8203,56128,56447]),!b);case"emoji":return b=d([55357,56424,55356,57342,8205,55358,56605,8205,55357,56424,55356,57340],[55357,56424,55356,57342,8203,55358,56605,8203,55357,56424,55356,57340]),!b}return!1}function f(a){var c=b.createElement("script");c.src=a,c.defer=c.type="text/javascript",b.getElementsByTagName("head")[0].appendChild(c)}var g,h,i,j,k=b.createElement("canvas"),l=k.getContext&&k.getContext("2d");for(j=Array("flag","emoji"),c.supports={everything:!0,everythingExceptFlag:!0},i=0;i<j.length;i++)c.supports[j[i]]=e(j[i]),c.supports.everything=c.supports.everything&&c.supports[j[i]],"flag"!==j[i]&&(c.supports.everythingExceptFlag=c.supports.everythingExceptFlag&&c.supports[j[i]]);c.supports.everythingExceptFlag=c.supports.everythingExceptFlag&&!c.supports.flag,c.DOMReady=!1,c.readyCallback=function(){c.DOMReady=!0},c.supports.everything||(h=function(){c.readyCallback()},b.addEventListener?(b.addEventListener("DOMContentLoaded",h,!1),a.addEventListener("load",h,!1)):(a.attachEvent("onload",h),b.attachEvent("onreadystatechange",function(){"complete"===b.readyState&&c.readyCallback()})),g=c.source||{},g.concatemoji?f(g.concatemoji):g.wpemoji&&g.twemoji&&(f(g.twemoji),f(g.wpemoji)))}(window,document,window._wpemojiSettings);
</script>
接下来我们来试试用爬虫下载一个小说——孔乙己,它的网址是 。该网址返回的是小说的纯文本格式,源代码和内容是一样的。
import requests
# 获取孔乙己数据
res = requests.get('https://apiv3.shanbay.com/codetime/articles/mnvdu')
# 以写入的方式打开一个名为孔乙己的 txt 文档
with open('孔乙己.txt', 'w') as file:
# 将数据的字符串形式写入文件中
file.write(res.text)
上面代码涉及到了文件操作,这里简单介绍一下。
open() 函数是 Python 中的内置函数,用于打开文件,返回值是一个 file 对象。
open() 函数接收的第一个参数为文件名,第二个参数为文件打开模式。打开模式默认为 r,是 read 的缩写,表示只读模式。即只能读取内容,不能修改内容。
常用的打开模式有 w(write,只写模式)、b(binary,二进制模式)和 a(append,追加模式,表示在文件末尾写入内容,不会从头开始覆盖原文件)。
Tips:在 w 和 a 模式下,如果你打开的文件不存在,那么 open() 函数会自动帮你创建一个。
这些打开模式还能两两组合,比如:rb 表示以二进制格式打开文件用于读取,wb 表示以二进制格式打开文件用于写入,ab 表示以二进制格式打开文件用于追加写入。
需要注意的是,使用 open() 函数打开文件,操作完毕后,最后一定要调用 file 对象的 close() 方法关闭该文件。所以一般我们像下面这样读写文件:
# 读取文件
file = open('文本.txt') # 打开模式默认为 r,可省略
print(file.read()) # 调用 read() 方法读取文件内容
file.close() # 关闭文件
# 写入文件
file = open('文本.txt', 'w') # 写入模式
file.write('生如夏花') # 调用 write() 方法写入内容
file.close() # 关闭文件
为了避免忘记调用 close() 方法关闭文件,导致资源占用、文件内容丢失等问题,推荐使用 with … as … 语法,它在最后会自动帮你关闭文件。
我们通过下面这个例子来对比一下
# 普通写法
file = open('文本.txt', 'w') # 写入模式
file.write('生如夏花') # 调用 write() 方法写入内容
file.close() # 关闭文件
# 使用 with ... as ... 写法
with open('文本.txt', 'w') as file:
file.write('生如夏花')
可以看到,使用 with … as … 语法后,关闭文件的操作就可以省略了。不仅代码简洁了,也避免了忘记关闭文件的问题。
了解了文件操作之后,我们重新回到之前爬虫的例子,再看一遍代码
import requests
# 获取孔乙己数据
res = requests.get('https://apiv3.shanbay.com/codetime/articles/mnvdu')
# 以写入的方式打开一个名为孔乙己的 txt 文档
with open('孔乙己.txt', 'w') as file:
# 将数据的字符串形式写入文件中
file.write(res.text)
我们获取到网页的响应后,以写入模式打开一个名为 孔乙己.txt 的文件,然后调用 write() 方法将响应内容的字符串形式写入到文件中,实现了小说的下载。同理,所有文本内容都可以通过这种方式进行下载,只需将 res.text 写入到文件当中保存即可。
3.res.content
除了文本内容的下载,爬虫还能下载图片、音频、视频等。我们来看一个下载图片的例子:
以下载上面的图片为例,图片地址是:http://pp.myapp.com/ma_pic2/0/shot_52575843_1_1618363820/0
下载图片的代码如下:
import requests
# 获取图片数据
res = requests.get('http://pp.myapp.com/ma_pic2/0/shot_52575843_1_1618363820/0')
# 以二进制写入的方式打开一个名为 info.jpg 的文件
with open('info.png', 'wb') as file:
# 将数据的二进制形式写入文件中
file.write(res.content)
我们可以看到,和下载小说的步骤几乎一样。区别在于图片是用二进制写入的方式,将数据的二进制形式写入文件当中,而不是字符串形式
所以 res.text 和 res.content 的区别是:res.text 用于文本内容的获取、下载,res.content 用于图片、音频、视频等二进制内容的获取、下载。
4.res.encoding
编码是信息从一种形式或格式转换为另一种形式的过程。
常见的编码方式有 ASCII、GBK、UTF-8 等。如果用和文件编码不同的方式去解码,我们就会得到一些乱码。
编码的发展史
我们都知道,计算机的底层是二进制。也就是说,计算机只认识 0 和 1。既然如此,计算机是如何展示文字、符号等信息的呢?
聪明的计算机科学家们想到了编码,将数字和文字、符号一一对应即可。比如 0000 对应 a,0001 对应 b,0010 对应 c(举个例子,实际上并不是这样对应的)。
因为英文字母比较少,加上常用的符号等,总共也就 100 多个。计算机科学家们用一个字节中的 7 位(总共 8 位)定义了一套编码,总共 128(2 的 7 次方)个字符,这就是 ASCII 编码。
随着科技的发展,计算机进入了欧洲国家。128 个字符对美国来说是够用的,但欧洲一些国家的语言,比如法语中,字母上方有注音符号,128 个字符就不够用了。因此欧洲国家决定将最后一个闲置的位也利用上,这样欧洲的编码就有 256(2 的 8 次方)个字符了。
但是不同的国家有不同的字母,这就导致前 128 个字符是一样的,后 128 个字符在不同的国家是不一样的。
不久后,计算机便来到了中国。中国的汉字可是有 10 万多个,256 个是远远不够的。中国计算机科学家们便重新定义了一套编码,也就是 GB2312,这套编码包含了 6763 个常用汉字和一些常用符号等。之后为了扩展能显示的汉字内容,还推出了 GBK 等编码标准。
你可能也发现问题了,每个国家都有自己的编码,还都不一样。发封电子邮件给外国人,在他们看来就都是乱码,这可怎么行!
于是,Unicode(统一码)便出现了。Unicode 是一个很大的集合,现在的规模可以容纳 100 多万个符号,并且每个符号的编码都不一样。
但是 Unicode 有个缺点,就是占用字节过多。英文字母本来只需要一个字节就够了,现在为了统一得用 3、4 个字节,很是浪费!因此导致 Unicode 在很长一段时间内无法推广,直到互联网的出现。
聪明的计算机科学家又想到了新的方法,推出了 UTF-8 编码,UTF-8 最大的一个特点,就是它是一种变长的编码方式。它可以使用 1~4 个字节表示一个符号,根据不同的符号而变化字节长度。
UTF-8 即解决了乱码问题,也解决了字节浪费的问题,是现在最常用的编码方式。
你看,编码的发展过程也是磕磕绊绊,一点一点地改进、发展至今的。
Python、爬虫课的学习也是如此,不必一口吃成胖子。慢慢来,一点一点地将知识点消化、吸收,总会有所收获的。
好了,我们回到爬虫的编码。
res.encoding 就是爬虫获取到数据的编码格式,requests 库会根据内容推测编码格式是什么,然后将 res.encoding 设成推测的格式,在访问 res.text 时使用该格式解码。
当推测的格式错误时,即出现乱码时,就需要我们手动给 res.encoding 赋值成正确的编码。我们来看下面的例子:
import requests
res = requests.get('https://www.baidu.com')
print(res.text)
#结果是<!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css><title>ç™¾åº¦ä¸€ä¸‹ï¼Œä½ å°±çŸ¥é“</title></head> <body link=#0000cc> <div id=wrapper> <div id=head> <div class=head_wrapper> <div class=s_form> <div class=s_form_wrapper> <div id=lg> <img hidefocus=true src=//www.baidu.com/img/bd_logo1.png width=270 height=129> </div> <form id=form name=f action=//www.baidu.com/s class=fm> <input type=hidden name=bdorz_come value=1> <input type=hidden name=ie value=utf-8> <input type=hidden name=f value=8> <input type=hidden name=rsv_bp value=1> <input type=hidden name=rsv_idx value=1> <input type=hidden name=tn value=baidu><span class="bg s_ipt_wr"><input id=kw name=wd class=s_ipt value maxlength=255 autocomplete=off autofocus=autofocus></span><span class="bg s_btn_wr"><input type=submit id=su value=百度一下 class="bg s_btn" autofocus></span> </form> </div> </div> <div id=u1> <a href=http://news.baidu.com name=tj_trnews class=mnav>æ–°é—»</a> <a href=https://www.hao123.com name=tj_trhao123 class=mnav>hao123</a> <a href=http://map.baidu.com name=tj_trmap class=mnav>地图</a> <a href=http://v.baidu.com name=tj_trvideo class=mnav>视频</a> <a href=http://tieba.baidu.com name=tj_trtieba class=mnav>è´´å§</a> <noscript> <a href=http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u=http%3A%2F%2Fwww.baidu.com%2f%3fbdorz_come%3d1 name=tj_login class=lb>登录</a> </noscript> <script>document.write('<a href="http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u='+ encodeURIComponent(window.location.href+ (window.location.search === "" ? "?" : "&")+ "bdorz_come=1")+ '" name="tj_login" class="lb">登录</a>');
</script> <a href=//www.baidu.com/more/ name=tj_briicon class=bri style="display: block;">更多产å“</a> </div> </div> </div> <div id=ftCon> <div id=ftConw> <p id=lh> <a href=http://home.baidu.com>å
³äºŽç™¾åº¦</a> <a href=http://ir.baidu.com>About Baidu</a> </p> <p id=cp>©2017 Baidu <a href=http://www.baidu.com/duty/>使用百度å‰å¿
读</a> <a href=http://jianyi.baidu.com/ class=cp-feedback>æ„è§å馈</a> 京ICPè¯030173å· <img src=//www.baidu.com/img/gs.gif> </p> </div> </div> </div> </body> </html>
我们来看看 requests 库推测的编码格式是什么:
import requests
res = requests.get('https://www.baidu.com')
print(res.encoding)
# 输出:ISO-8859-1
我们可以看到,requests 库将编码错误地推测成了 ISO-8859-1 格式。国内网站的编码格式一般都是 UTF-8、GBK 或 GB2312。
上述代码中网站的正确编码格式其实是 UTF-8,我们需要手动将编码修改成 UTF-8,便能显示正确的内容了。
import requests
res = requests.get('https://www.baidu.com')
res.encoding='utf-8'
print(res.text)