本篇教程内容完全针对初学者,如果你需要更进阶一点的知识,本篇可能给你的帮助十分有限。
准备工作
首先确认代码环境,我们使用python来进行爬虫的开发。在这里我使用的版本是python3.5。这个教程应该适用于所有python3.x版本, python2.x可能做少许的改动就可以直接运行。
这次教程中我们要用到的模块是requests模块。如果没有安装requests模块的同学需要先安装一下。
Mac端的同学先打开terminal,Windows端的同学可以按下'win + r'后调出"运行",然后输入cmd打开命令指示行。
打开之后输入
pip install requests
来安装这个模块。
requests是一个非常方便的模块。可能你会看到有很多的代码在使用urllib的模块,这个教程没有使用这些模块的原因是因为它们相比较requests而言都比较复杂,作为一个初学者,我们没有必要一味的攀高。往往比工具更重要的是思想,我们学习的主要是思想,而非工具本身。真正的收获,一定是你忘记你的所有所学之后剩下的东西。
好了我们不说废话了,开始进入正题吧!
小试身手
我们先从简单的地方开始——爬取百度的首页。
打开python,输入下面的代码
import requests
r = requests.get('https://baidu.com')
print(r.text)
你看,百度的界面我们就已经拿到了
可能有的同学一开始这样爬会得到一个timeout的错误。如果出现了这样的情况,就是网站怀疑是一个机器人在访问自己,所以作出了一定的阻止。
那怎么办呢?没有关系,我们稍微修改一下我们的代码,改成
headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'}
r = requests.get('https://baidu.com', headers = headers)
headers的意思就是告诉网站,我们是一个正常的浏览器在给它发送信息,请它给我们正确的信息。
同样的,很多网站不需要登录,直接就可以访问其中的内容,比如糗事百科、煎蛋网等等都可以直接爬取下来。
怎么样,是不是有一丝小小的成就感?
你可能就会问了,网页文件里面有那么多的无用信息,我们要怎么把它提取出来呢?
这个时候我们通常有两种做法,一种是正则表达式,一种是通过网页的结构对内容提取。
因为正则表达式相较后者更为复杂,对新手并不十分友好。所以我们这次的爬虫使用直接对网页的内容进行提取的方法来获取信息。如果你很想了解正则表达式的使用方式,你可以期待本教程的后续更新或者崔庆才老师博客中的正则表达式教程。
Beautiful soup 的安装
Beautiful soup是另一个python的模块,我们将用这个模块来分解网页的结构,并对其中的内容进行提取。
同样的,Beautiful soup是一个第三方模块,我们需要使用
pip install beautifulsoup4
来对模块进行安装。
但是这还不够,Beautiful soup需要lxml包对文件进行处理,所以在安装完bs4之后你还需要安装lxml包:
pip install lxml
踏上正轨
在进一步讲Beautiful soup的使用之前,我们先分析一下要爬取的网页的结构,这样我们才能更加有效的针对网页的结构对网页的内容进行提取。
这里我们以糗事百科为例进行讲解。
第一件事仍然是我们先把它的页面爬取下来,也就是
import requests
headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'}
r = requests.get('http://www.qiushibaike.com', headers = headers)
content = r.text
print(content)
可以看到网页的内容已经被我们储存到了content变量中,打印出来是这样的:
接下来我们来分析网站的结构。
可能你也发现了,直接使用我们打印出来的结果分析起来十分吃力。所以我们使用更加高效的工具——开发者工具(Developer tools)来进行分析。
通常来说任何一个浏览器都有开发者工具,这里我们以Chrome为例。
Mac端在打开糗事百科之后按下option+command+I, Windows端直接按下F12即可。
可以看到,只要我们鼠标移到对应的标签(HTML tag)上,chrome就会帮我们把标签里面包含的网页内容高亮出来。
我们要的内容很简单,就是里面的段子。所以我们右键点击段子所在的位置,点击审查元素(Inspect),chrome就会自动找到该内容对应的标签。
可以看到我们要的段子的内容就储存在这个叫做span的标签中。
我们再往上追寻,可以看到<span>标签是属于一个叫做<div class="content">的标签的。继续往上我们可以看到一个叫做<div class="article block untagged mb15" id =....>的标签。
点击旁边的小三角,合并标签里面的内容之后我们可以看到有非常多这样格式的标签。而且每一个标签都对应了一个段子。
所以很显然,我们只要把这样的标签都提取出来,我们就可以得到糗事百科中的段子了。
所以现在我们明确了方向——把所有class为article block untagged mb15的div标签找到,然后获取里面span标签的内容。这就是我们要找的段子了。
那要怎么写呢?
首先我们把我们需要的内容转换到Beautiful soup中。
# 引入Beautiful Soup包
from bs4 import BeautifulSoup
# 把刚刚保存在content中的文件放入Beautiful Soup中
soup = BeautifulSoup(content, 'lxml')
你可能会奇怪后面的'lxml'是什么意思。其实这是因为我们的content是一个字符串(string)数据,它并不是网页文件。所以这里我们告诉Beautiful soup说你就把他当一个网页文件来处理就行。
到目前为止, 我们已经把网页的内容放入了Beautiful soup中,接下来就是施展Beautiful soup的魔法,把网页分成一块块的内容了。
施展魔法
注意,在进行下一步之前你应该确认一下目前我们的代码是这样的:
import requests
from bs4 import BeautifulSoup
headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'}
r = requests.get('http://www.qiushibaike.com', headers = headers)
content = r.text
soup = BeautifulSoup(content, 'lxml')
还记得吗?我们分析的结果是说所有的段子都在网页中class为article block untagged mb15的div标签中的span标签中。看起来好像有点复杂,没事,我们一步一步来做。
首先我们分解出所有class为article block untagged mb15标签:
divs = soup.find_all(class_ = 'article block untagged mb15')
我们可以打印出divs看看是什么样子的。
print(divs)
可以看到,所有div的标签都已经储存在divs里面了。
在进行下一步之前我们再确认一下,这个时候你的代码应该是这个样子的:
import requests
from bs4 import BeautifulSoup
headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'}
r = requests.get('http://www.qiushibaike.com', headers = headers)
content = r.text
soup = BeautifulSoup(r.text, 'lxml')
divs = soup.find_all(class_ = 'article block untagged mb15')
print(divs)
接下来我们要做的事情就是把这些div里面的span都取出来。
我们先把最后一行去掉,避免不必要的打印。然后提取出每个div里面的span
for div in divs:
joke = div.span.get_text()
print(joke)
print(‘------’)
注意,这个时候你的代码看起来应该是这样的:
import requests
from bs4 import BeautifulSoup
headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'}
r = requests.get('http://www.qiushibaike.com', headers = headers)
content = r.text
soup = BeautifulSoup(r.text, 'lxml')
divs = soup.find_all(class_ = 'article block untagged mb15')
for div in divs:
joke = div.span.get_text()
print(joke)
print('------')
这段代码的意思就是把divs中的每个div都取出来(还记得吗,divs里面有所有的class为article block untagged mb15的div)。取出来之后我们对它说,你把你里面叫做span的标签的文字都给我吧。于是我们就把这里面的笑话都放进了joke当中,并打印了出来。
运行一下程序,可以看到你已经成功把糗事百科首页上面的所有段子爬下来了!
恭喜你已经成功入门了python的爬虫。你现在可以给自己鼓鼓掌👏
爬虫并没有那么难,对吗?
one more thing
我们把打印的内容拉到最后面,发现一些读不通的语句:
我们回到糗事百科的首页看看
原来是有图片,怪不得光看字看不懂。那我们的爬虫现在还不能爬下来图片,那有没有什么办法让这些莫名其妙的话都删掉呢?
我想你也应该想到了,同样的,我们去分析网页的结构,然后告诉python,如果存在带有图片的网页结构,我们就不打印这个段子。
我们打开开发者工具。(你还记得快捷键吗?)然后右键点击图片,审查元素。
我们看到,图片的网页标签(HTML tag)是img,所以我们只要在代码中检测div中是否有img标签,我们是不是就知道是不是要打印这条段子了呢?
别急,我们先看看把div里面的img都打印出来是什么样子的。
for div in divs:
# 在这里我们找到所有的img标签,然后打印
print(div.find_all('img'))
joke = div.span.get_text()
print(joke)
print('------')
看来不行, 我们看到几乎每一条里面都有图片。我们回到chrome再看看div里面的结构,原来图片不只段子里面的图片,还包括了发帖人的头像,评论区的点赞图标等等。
那看来仅仅是看是否img标签来判断还是不够的,那我们就只好再看看别的结构,看看有没有更有效的结构。
段子图片的img标签再往上找几层我们不难发现,它们都存在一个<div class="thumb">的标签之下。而那些没有图片的段子就没有<div class="thumb">这个标签。
所以你成功发现了解决的方法——只要找每个总的div里面是否有<div class="thumb">标签就知道这个段子里面是否包含图片了。
那我们继续修改我们的循环:
for div in divs:
if div.find_all(class_ = 'thumb'):
continue
joke = div.span.get_text()
print(joke)
print('------')
if div.find_all(class_ = 'thumb')
的意思就是检查div.find_all(class_ = 'thumb')
中有没有找到class为thumb的标签,如果找到了,那就说明我们不打印这一段,所以执行continue
。continue
的意思就是跳到循环末尾,直接进入下一层循环,中间的代码都不要执行了,所以我们的代码最后看起来是这样的:
import requests
from bs4 import BeautifulSoup
headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'}
r = requests.get('http://www.qiushibaike.com', headers = headers)
content = r.text
soup = BeautifulSoup(r.text, 'lxml')
divs = soup.find_all(class_ = 'article block untagged mb15')
for div in divs:
if div.find_all(class_ = 'thumb'):
continue
joke = div.span.get_text()
print(joke)
print('------')
运行一下,可以发现我们已经已经看不到有图片的段子了。
恭喜你,你已经学会了如何去分析网页的结构,这是非常重要的一步,学会分析的方法比学会任何工具都要有用。
而且你做的非常好!你现在可以去喝一杯咖啡,吃点饼干,休息一下。
如果你精力还很充沛,不妨思考一下,能不能把每个段子的作者,点赞数,评论数都提取出来呢?
Bonus
到目前为止,我们已经成功的提取了糗事百科首页的段子。可能精力充沛的同学还成功提取了每条段子的作者、点赞数等等。
笑话永远是不嫌多的~可能你觉得只看一页的笑话已经满足不了你了,那我们就来试试提取前四页的内容。
我们先点开糗事百科的第二页,看看和第一页有什么不同。
可以看到网址发生了变化。从http:qiushibaike.com
变成了一长串的http://www.qiushibaike.com/8hr/page/2/?s=4969792
。注意到网址中有/page/2/
,page是页的意思,很有可能就是这一块在控制页码。
光猜肯定不行,我们来试一试把2改成3会发生什么。
第二页变成了第三页,那看来确确实实就是/page/之后的数字在控制页码。但是除了page之外,网址后面还跟着一串很奇怪的东西——?s=4969792
,那这么一长串是在干什么呢?
这就和我们下一节要讲的GET和POST有关了。我们在进入下一节之前简单介绍一下。
在访问糗事百科的时候我们的浏览器会给网站发送一个识别码,这个识别码就是后面的那一串数字,说明一直是你在访问它。
某种意义上讲这样的识别码也可以帮助糗事百科分析用户习惯,因为一个识别码对应一个用户,通过这个识别码访问糗事百科的内容的时候,糗事百科就知道你的用户行为。
当然,如果你换了一串数字,访问也是可以正常进行的。比如我随便一改后面的这串数字,
网站还是正常的出现了。那这个s=1231421
就是浏览器在发送GET请求的时候的附加数据。换句话讲,就是浏览器在给网站说,麻烦你给我一下最新的笑话,要第二页的。对的,我还是刚刚的那个王老五。
那如果你改了后面的这串数字,就相当于给网站说,我其实是隔壁老王,不是刚刚的那个王老五。不过还是麻烦你把第二页的笑话给我看一看。
所以你现在明白了,GET就是浏览器在向网站索要网页信息,在索要网页的同时也可能会发送一些信息给网站。
如果我们删掉?s=xxxxx
,可以看到网页还是会正常的加载。也就是在浏览器在发送GET请求的时候没有附加任何的信息。
还记得我们最开始的目标吗——拿到糗事百科前四页的段子。所以这个时候我们只需要通过循环对网址进行一个修改,分别访问糗事百科四次,提取四次就可以了。
知道了方法,我们现在就用代码来实现它。
在这里我强烈建议你暂停一下,不要往下翻。试试自己写能不能把代码写出来,如果实在不行再看看下面的代码。
编程是一门动手的艺术,橘子只有自己尝尝才知道是什么味道~
import requests
from bs4 import BeautifulSoup
headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'}
base_url = 'http://www.qiushibaike.com/8hr/page/' # 设定一个网址不变的部分,然后我们只要每次在这个后面加数字就可以了
for num in range(1, 5): # 设置循环,让num分别等于1-10
print('第{}页'.format(num))
r = requests.get(base_url + str(num), headers = headers) #这里对网址进行一个修改
#剩下的部分都是和原来的代码一样
content = r.text
soup = BeautifulSoup(r.text, 'lxml')
divs = soup.find_all(class_ = 'article block untagged mb15')
for div in divs:
if div.find_all(class_ = 'thumb'):
continue
joke = div.span.get_text()
print(joke)
print('------')
运行一下,可以看到你已经成功的拿到了前四页的笑话。
返回导航
本篇教程代码文件业已上传Github,点击这里访问