「这是我参与2022首次更文挑战的第12天,活动详情查看:2022首次更文挑战」。
前言
爬虫是 Python 的一大应用场景,因为 Python 语法简单,实现方便,而且有大量方便的模块。有时候我们写一个爬虫只需要几行十几行代码就能实现一个爬虫。
今天我们用 Python 写一个爬虫,爬取表情包网站。
一、Requests 模块详解
我们在写爬虫时通常有下面几个步骤:
- 请求网页源码
- 分析网页
- 获取 url
- 下载资源
有时候我们还需要解决一些反爬机制,这种情况我们需要做的事情就更多了。
而 requests 模块是我们在写爬虫时经常会用到的一个模块,我们可以用它来发送网络请求。获取网页源码,然后解析出图片或者视频资源的 url,再爬取资源。
也就是说,请求在请求网页源码和下载资源我们都需要用到 requests 模块。那具体要怎么使用呢?下面我们来看看。
1.1 发送请求
首选我们需要安装 requests 模块:
pip install requests
安装完成后就可以在我们的程序中使用了。我们先看下面代码:
import requests
# 发送网络请求
resp = requests.get("http://www.baidu.com")
# 获取网页源码
print(resp.text)
上面我们写了两句代码,首先调用了 requests.get 函数。它的作用是请求指定 url 的数据。我们这里请求的是百度主页。请求完成后,会给我返回一个响应结果,里面包含了很多数据。我们现在只需要看看 resp.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=http://s1.bdstatic.com/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></span><span class="bg s_btn_wr"><input type=submit id=su value=ç¾åº¦ä¸ä¸ class="bg s_btn"></span> </form> </div> </div> <div id=u1> <a href=http://news.baidu.com name=tj_trnews class=mnav>æ°é»</a> <a href=http://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 的一些其它操作。
1.2 请求参数
很多时候我们发送请求需要带一些参数,使用 requests 模块,能很容易实现带参数的请求:
import requests
params = {
"wd": "你好",
"type": "en"
}
# 发送网络请求
resp = requests.get(
"http://www.baidu.com",
params=params
)
# 获取网页源码
print(resp.text)
我们还是调用了 get 函数,但是这次我们使用了一个 params 参数,该参数接收一个字典,内容就是我们参数的键值对。我们可以通过这种方式传多个参数。
1.3 设置请求头
很多时候我们发送请求会被拒绝,因为在发送请求的时候我们带了一个 python 的请求头:
import requests
resp = requests.get("http://www.baidu.com")
print(resp.request.headers)
我们可以通过上面的代码查看请求头,输出如下:
{'User-Agent': 'python-requests/2.25.1', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive'}
可以看到我们是通过 python 的 requests 模块发送请求,而不是通过浏览器发送请求。这个时候就会有许多网站拒绝我们发送请求,因此我们需要修改请求头信息。
我们打开浏览器,右击鼠标 -> 检查 -> network,然后随便刷新一个网页:
我们可以在右下角看到 User-Agent 字眼,这就是我们浏览器的请求头,我们只需要复制到 python 中,设置成自己的请求头即可:
import requests
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36"
}
resp = requests.get("http://www.baidu.com", headers=headers)
print(resp.request.headers)
这次的输出结果如下:
{'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive', 'Cookie': 'BAIDUID=8B23A1B46FBF8F2D277191005969C97B:FG=1; BIDUPSID=8B23A1B46FBF8F2DFC7C514AC3B2CA47; PSTM=1616684009; BD_LAST_QID=16997148536356617163'}
可以看到已经不再是 python-requests 模块了。
1.4 下载图片
图片资源的请求和网页的请求是一样的,只是图片只有我们只能通过二进制的方式获取并保存。我们看如下代码:
import requests
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36"
}
resp = requests.get("https://images.gitbook.cn/8fdc22a0-8d79-11eb-b199-2b2044ad88e3", headers=headers)
with open("img.jpg", "wb") as f:
f.write(resp.content)
我们还是使用 get 方法,然后打开了一个文件并以二进制的方式写入。这里需要注意一下,我们写入的内容是 resp.content,也就是获取的二进制数据。运行程序后目录下就会多一个 img.jpg 图片了。
二、Xpath 语法
上面我们已经完成了请求的操作,下面我们来看看解析网页元素的操作。
xpath 是解析 xml 的一种语法,但是我们也可以用来解析 html 标签。掌握 xpath 可以很大程度上方便我们爬虫的操作。
2.1 绝对路径
我们来看下面如下网页:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>
<p>你好</p>
<p>你好</p>
<p>你好</p>
</div>
</body>
</html>
假如我们要寻找 p 标签,就可以按照如下路径/html/body/div/p。 其中/表示根路径,即/html/body/div/p 的含义是网页下的 html 标签下的 body 标签下的 div 标签下的 p 标签。
这样我们就可以寻找很多内容了,比如寻找 title 标签,我们只需要写成/html/head/title 即可。
2.2 相对路径
不过绝对路径还是有很多不便的地方,有时候我们找一个元素需要写很长一串内容。而相对路径就没这么多事了。
比如我想要寻找所有的 p 标签,语法如下://p,其中//就表示相对路径。再比如我想要寻找所有的 div,只需要写成//div 即可。
有时候我们可以相对路径和绝对路径结合起来使用,比如我们需要在如下网页:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>
<p>你好</p>
<img src="test.jpg"/>
<p><img src="test.jpg"/></p>
<p>你好</p>
</div>
</body>
</html>
寻找 p 中的 img 标签,但是网页中有两个 img 标签,这个时候我们就不能直接使用相对路径了。但是我又不想写一大串绝对路径,我们就可以写成://p/img
。
2.3 属性筛选
不过上面两种方式还是不能满足我们爬虫的需求,因为我们爬取的页面通常非常复杂,通过上面的方式无法精确定位到我们需要的元素。这个时候我们就可以进行属性筛选了。我们看如下页面:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>
<img src="1.jpg">
</div>
<div id="img">
<img src="2.jpg">
</div>
</body>
</html>
如果要选择 id 为 img 的 div 下的 img 标签,用相对路径和绝对路径都是行不通的。这个时候我们可以考虑用属性筛选,我们可以写成://div[@id="img"]/img,我们先找到相对位置的 div,然后通过[@id="img"]筛选 id 为 img 的 div,然后再定位该 div 下的 img 标签,这样就成功定位了我们需要的元素。
除了 id,我们还可以通过其它属性来定位,写法都是一样的。
三、Beautifulsoup 模块
3.1 寻找所有元素
Besutifulsoup 是用来解析 html 的模块,使用它我们可以很快定位到我们需要的元素。这个模块同样需要我们手动安装:
pip install beautifulsoup4
我们用下面的网页作为例子,进行练习:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>
<img src="1.jpg">
</div>
<div id="img">
<img src="2.jpg">
</div>
</body>
</html>
现在我们的任务是找到所有的 div。使用 BeautifulSoup 操作如下:
from bs4 import BeautifulSoup
# 读取网页
with open("test.html", "r", encoding="utf-8") as f:
text = f.read()
# 创建 bs 对象
soup = BeautifulSoup(text, "lxml")
# 找到所有的 div
divs = soup.find_all("div")
# 遍历 div
for div in divs:
print(div)
首先我们创建了一个 bs 对象,传入文本数据已经固定参数 lxml。第二个参数我们也可以选择 html.parser,相比之下 lxml 效率要更高。
然后我们调用了 find_all 方法,传入需要定位的标签。数据结果如下:
<div>
<img src="1.jpg"/>
</div>
<div id="img">
<img src="2.jpg"/>
</div>
我们也可以调用 find 方法,它会查找页面中一个符合规范的元素。
3.2 属性筛选
bs 同样可以属性筛选,操作也十分简单,我们还是用上面的网页,这次我们要查找 id 为 img 的 div:
from bs4 import BeautifulSoup
with open("test.html", "r", encoding="utf-8") as f:
text = f.read()
soup = BeautifulSoup(text, "lxml")
divs = soup.find_all("div", {"id": "img"})
for div in divs:
print(div)
这次我们给 find_all 方法传了一个参数,参数类型是一个字典。参数含义是属性和属性值的键值对,结果我们找到了下面的内容:
<div id="img">
<img src="2.jpg"/>
</div>
可以看到成功定位了元素。
3.3 获取元素属性
当然,我们最终的目的是获取元素的文本或者属性。所以下面我们来看看获取属性的操作。我们对页面进行简单的修改:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>
<img src="1.jpg">
</div>
<div id="img">
<img src="2.jpg">
<p>你好</p>
</div>
</body>
</html>
这次我们的任务是获取图片的 src 和 p 标签中的文本,代码如下:
from bs4 import BeautifulSoup
with open("test.html", "r", encoding="utf-8") as f:
text = f.read()
soup = BeautifulSoup(text, "lxml")
div = soup.find("div", {"id": "img"})
print(div.find("img").get("src"))
print(div.find("p").text)
前面的操作没有改变,我们还是通过 id 找到了我们需要的图片。然后通过 get 方法获取图片的 src,并输出.text 属性。 输出结果如下:
2.jpg
你好
可以看到成功获取了我们需要的内容。但是 BeautifulSoup 在效率上要差一些,在实际应用中我们使用更多的是 lxml。
四、etree 模块
etree 是是 lxml 的一个子模块,它是通过 xpath 语法查找元素的模块,在效率上要优于 BeautifulSoup。我们来看看 etree 模块如下使用吧。
在使用前我们需要安装 lxml 模块:
pip install lxml
我们使用下面页面:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>
<img src="1.jpg">
</div>
<div id="img">
<img src="2.jpg">
<p>你好</p>
</div>
</body>
</html>
我们来尝试寻找 p 标签,并获取 p 的内容。因为我们的页面只有一个 p,我们可以直接通过相对路径来定位。xpath 可以写成://p/text()。这里我们多写了个 text(),意思就是获取查找到元素的文本内容,这里的意思就是获取 p 标签内部的文本。
然后我们开始使用 etree 模块定位元素:
from lxml import etree
# 读取页面源码
with open("test.html", "r", encoding="utf-8") as f:
content = f.read()
# 创建_Element 对象
html = etree.HTML(content)
# 定位元素
p = html.xpath("//p/text()")
# 输出结果
print(p)
我们先是通过 etree.HTML 创建一个 html 对象,创建时我们需要传入 html 代码。有了这个对象后,我们就可以调用 xpath 直接传入 xpath 语法进行我们想要的操作了。 下面是输出结果:
['你好']
如果我们要定位其它元素,只需要写出一个比较有效的 xpath 语法即可了。下面我们来实际应用一下。
五、爬虫实战
我们以一个表情包网站为例,下面是网站主页:
我们可以右击检查查看网页源码。可以看到,图片被包含在一个 div 中,我们把我们把这部分代码拿下来:
<div class="bqppdiv" style="vertical-align: middle;">
<a href="/biaoqing/detail/id/650901.html" title="在消遣你爹得感情试试(熊猫头表情包)">
<img class="ui image lazy" data-original="http://wx4.sinaimg.cn/bmiddle/006C7PHRly1gem1knxqo5j30u00trmxk.jpg"
src="/Public/lazyload/img/transparent.gif" alt="在消遣你爹得感情试试(熊猫头表情包)_消遣_熊猫_试试_感情表情-发表情"
style="max-height:188;margin: 0 auto"/>
<p style="display: block;height: 0;width: 0;overflow: hidden;">在消遣你爹得感情试试(熊猫头表情包)_消遣_熊猫_试试_感情表情</p>
</a>
</div>
首先我们可以观察到 div 的 class 是 bqppdiv,我们可以在浏览器上查看一下,发现每张表情包都包含在一个 class 为 bqppdiv 的 div 中。这样我们就可以通过属性定位该 div,我们继续观察发现 div 的内部只有一个 image,于是我们直接通过相对路径即可查找到 img 元素。
但是我们需要的是图片的 url,我们观察 img 标签可以发现 data-original 属性是图片的 url 信息,于是我们可以写出如下 xpath 语法://div[@class='bqppdiv']//img/@data-original
,这里我们有一个新内容,即 @data-original
,这里的作用是获取元素的属性值,我们还可以通过 @
获取其它属性值。
有了 xpath 语法,下面的事情就好办了:
import requests
from lxml import etree
# 要爬取的网址
url = "https://www.fabiaoqing.com"
# 发送请求
resp = requests.get(url)
# 创建 html 对象
html = etree.HTML(resp.text)
# 获取图片 url
srcs = html.xpath("//div[@class='bqppdiv']//img/@data-original")
# 遍历 url
for src, i in zip(srcs, range(len(srcs))):
with open("imgs/%s.jpg" % i, "wb") as f:
f.write(requests.get(src).content)
首先我们通过 requests 模块发送网络请求,获取网页源码。然后通过 etree 定位元素,最后再通过图片的 url 获取图片数据并下载到本地。 运行程序后,可以在 imgs(需要手动创建一个 imgs 目录)目录下看到我们爬取的图片:
当然这是比较简单的爬虫。大家可以找一些网址自己练习一下。