最近由于公司业务的需要,我开始做了一些简单的爬虫的工作。学了爬虫之后,就在想,能不能用爬虫做一些有趣的事情呢?想来想去,还是教大家写个简单的爬虫,用来爬取一组美女图。

因为只有美女图才能吸引你们这些绅士过来围观。。。。。

最后附上源码,拿去直接就可以用了。

目标

我在 bing 上搜索 美女图,出来了这个。

搜索美女图

那我们就用这个来开刀吧。

美女图首页

额。。。。。。。。

这尺度,我们还是选个保守一点的来做例子吧。。。。。。

美女图例子

配置环境

由于我们是使用 python 来做爬虫的,所以我们要先装好 python 环境,这个比较简单,直接到官网下载安装就可以了,安装的过程也是一直下一步就行。

下载地址: https://www.python.org/downloads/

美女图例子

我们选择相应的操作系统平台安装就可以了。

还有一个是 python 的网络请求库 requests ,我们后面将要使用这个库进行网络请求,来爬取数据。

这个库的安装也很简单,在命令行下执行以下命令就可以了

pip3 install requests

还有一个是用来解析 html 结构,和提取元素内容的一个库。安装也很简单

pip3 install beautifulsoup4

开始分析

在 Chrome(谷歌浏览器) 下,打开我们要爬取的地址 https://www.walltu.com/tuku/201801/212736.html , 然后按 F12 ,在网络这一栏,我们可以看到这个页面的请求。

查看网络请求

然后,再切到 XHR 下,发现这个页面并没有发 ajax ,也就是说,我们要爬取的图片地址和页面可能是在一起的。

我们看一下这个 html 页面里面是什么东西。

html 内容

然后我们在使用元素选择器,看一下我们要爬取的图片是哪个元素,然后我们再到 html 代码里面找一下,找到这个图片的元素

查找元素

找到了,然后我们再到网络请求到页面里面看一下,看能不能找到这个元素,果然找到了。

在 html 中找到这个元素

我们再浏览器直接打开这个地址:

浏览器打开图片地址

那就说明可以通过地址直接获取到图片,那我们就开始了。

开始干活

到这里,我们就要开始正式写代码了,但是不要担心,很简单的。老司机带路肯定是稳的!

先从下载图片开始

我们先试一下,看能不能直接请求到图片。打印一下请求的状态码和内容。

import requests

response = requests.get('https://img.walltu.com/d/file/shutu/2018-01/20150813115821611.jpg!800')
print(response.status_code)
print(response.content)

打印结果如下:

403
b'{"code":"40310014","msg":"invalid Referer header"}'

请求失败了,但是提示我们 referer 非法。说起来也是挺好笑的,居然会提示我们错误的地方,这是怕我们爬不到数据吗。。。。。。

知道问题在哪就好搞了。我们找一下这个信息要填什么。

切换之前页面的调试界面,选中 img ,看一下页面上请求时用的是什么,照抄过来就行了。

查找 referer

然后把这个东西加到请求头试一下

import requests

headers = {
    'Referer':'https://www.walltu.com/tuku/201801/212736.html'
}
response = requests.get('https://img.walltu.com/d/file/shutu/2018-01/20150813115821611.jpg!800',headers=headers)
print(response.status_code)
print(response.content)

上面的代码应该很容易看懂吧,即使没有学过 python 。就是在请求的时候多添加了一个请求头的参数

打印内容如下:

200
b'\xff\xd8\\xea\xe3o\x88...............1\x12\xc1\x1b\x19)\xbcw\x139u\x0eLD\xd8l\x02o\x81\x12c&\xc4\xc9\xf2MT\x7f\xff\xd9'

我们看到状态码是 200 ,说明成功了,后面打印的是图片的二进制数据,由于图片转化成二进制内容很多,中间的部分信息被我省略了。

保存图片

刚才我们通过 requests 请求到了图片,但是二进制的图片给我们没有用啊,我们要看图片,真正的图片!

那我们就要把图片保存起来。我们先打开一个文件,然后把请求到的内容写入到文件里面就行了。

import requests

headers = {
    'Referer':'https://www.walltu.com/tuku/201801/212736.html'
}
response = requests.get('https://img.walltu.com/d/file/shutu/2018-01/20150813115821611.jpg!800',headers=headers)
# 打开一个文件,第一个参数是文件路径,第二参数是打开方式,'wb' 表示以二进制写入的方式打开
img_file = open('img.jpg','wb')
# 然后往文件里面写内容
img_file.write(response.content)

这段代码执行完,应该就可以在同个目录下找到下载好的 img.jpg 图片,打开就能看见下载好的图片的了。是不是很简单呢。

目标是爬取一组图

我们搞半天,只是把一张图片下载到本地而已,这么麻烦,还不如直接在页面上右击保存图片来得快呢。。。。。。

我们的目标可不是下载一张图片,而是一整组图片!

从第一张图片开始爬取

第一张图片刚才不是下载完了?

不不不,我们要是输入这一组图片的网页地址,就可以下载这一组图片的效果。也就是输入 https://www.walltu.com/tuku/201801/212736.html 就可以下载一整组图的效果。

要实现这个效果,可就不能像之前那样手动去找图片的地址了,要通过代码找到图片的地址。那我们就需要使用一些工具来帮我们找到这个地址了,我这里使用的是 BeautifulSoup 来进行元素查找。

我们先看一下刚才查找的那个图

查找元素

观察这个结构,发现比较靠近目标并且有特点的元素是 <dd class="p"> ,我们只要找到这个元素,再顺藤摸瓜,往下找到下面的 <p> 然后再往里面就可以找到我们要的 <img> 这个元素了。

分析结构

代码上,我们可以这么写:

import requests
from bs4 import BeautifulSoup
# 请求这个页面
response = requests.get('https://www.walltu.com/tuku/201801/212736.html')
# BeautifulSoup 的构造函数最少需要两个参数,第一个是要解析的内容,第二个是解析器,这里我们使用的是 'html.parser'
content = BeautifulSoup(response.content, 'html.parser')
target_parent = content.find('dd', class_='p')
target = target_parent.p.img
img_address = target['src']
print(img_address)

打印结果如下:

https://img.walltu.com/d/file/shutu/2018-01/20150813115821611.jpg!800

我们这样就拿到了第一个页面的图片地址。

查找下一个

我们的最终目标的是爬取一整组图。找到了第一个图片,那就要准备找下一个了。

下一个图

我想很多人想到的是图片右边的 下一图 这个按钮,但是有个问题,就是到这组图的最后一张后,这个按钮的链接就会变成下一组图的地址了,这样就会一直循环下去,这可不行。

我把目标转到了下面。

选图

我们发现当前的图片是有个选中的样式的,我们应该可以找到这个元素,然后找到下一个元素,也就是下一个图片的页面的链接。这个会不会有最后一张的地址变成下一组的地址 这个问题呢?

我们看一下:

会不会有问题?

好像没有,最后一个还是最后一个,没有多出一个是跳下一组图的元素。我们还得看一下 html 。

查看 html

确实没有一些多余的东西,而且我们还发现了选中的样式是 class="c",那就开始干活!

import requests
from bs4 import BeautifulSoup
# 请求这个页面
response = requests.get('https://www.walltu.com/tuku/201801/212736.html')
# BeautifulSoup 的构造函数最少需要两个参数,第一个是要解析的内容,第二个是解析器,这里我们使用的是 'html.parser'
content = BeautifulSoup(response.content, 'html.parser')
# 找到当前选中的图片
index = content.find('a', class_='c')
# 找到下一个图片的元素
next_target = index.next_sibling
if next_target is None:
    # 没有下一个了
    print('没有下一个了')
else:
    # 找到了下一个,我们就打印出来
    print('下一个')
    next_addr = 'https://www.walltu.com' + next_target['href']
    print(next_addr)

打印结果:

下一个
https://www.walltu.com/tuku/201801/212736_2.html

如果是最后一个呢?我们也看一下,我们把地址换成 https://www.walltu.com/tuku/201801/212736_7.html

再执行一次,打印结果

没有下一个了

再配合上之前下载图片的代码,应该就可以把一整组图给下载下来了。我们还需要一个文件夹把这些图片给整理放好,其实还有剩下的两步工作,就是新建一个文件夹,用来存放图片,然后下载图片的时候再给每个图片命名,否则重复的文件名会把之前的图片给覆盖掉。

新建文件夹

新建文件夹很简单

import os
# 创建文件夹
os.makedirs('文件夹的名称')

用来存图片的目录名就用第一个页面的标题吧。

给图片命名

要给图片命名,就要考虑唯一性,从图片的下载地址上看,这些名称确实是唯一的,但是都是一些随机的名称,也就是没有顺序,感觉也不太好,因为这些图片实际上是有一定顺序的。

实在不行,那就用页面的地址吧。因为一个图片对应的也是一个页面,而且我们看一下这些页面地址。

https://www.walltu.com/tuku/201801/212736.html
https://www.walltu.com/tuku/201801/212736_2.html
https://www.walltu.com/tuku/201801/212736_3.html
https://www.walltu.com/tuku/201801/212736_4.html

我们发现,这些还是有顺序的,刚好满足我们的需求。

在下载图片的时候,传入名称就行了。这里就不上代码了。

整理一下思路

  1. 根据第一个页面的标题新建一个文件夹,用来存放图片。
  2. 解析页面,下载第一张图片,获取下一个页面的地址
  3. 循环第二步,直到下载完最后一个图片。

整理一下代码

我们最后整理一下代码,把一些操作抽取成方法。

import os
import requests
from bs4 import BeautifulSoup

# 下载所有图片
def downloadAllImg(address):
    # 解析页面
    content = getContent(address)
    # 获取页面标题
    title = content.title.string
    # 新建文件夹
    os.makedirs(title)
    # 获取图片的地址
    download_img_addr = getDownloadImgAddr(content)
    # 获取文件名
    file_name = getFileName(address)
    print('开始下载:'+title)
    while download_img_addr is not None:
        # 下载图片
        downloadImg(download_img_addr,address,title,file_name)
        # 获取下一个页面的地址
        next_addr = getNextAddr(content)
        if next_addr is None:
            download_img_addr = None
        else:
            # 获取下一个图片的名称,并赋值给 变量
            file_name = getFileName(next_addr)
            # 解析下一个页面,并赋值给 变量
            content = getContent(next_addr)
            # 获取下一个图片的下载地址,并赋值给 变量
            download_img_addr = getDownloadImgAddr(content)
            print('新下载地址:'+download_img_addr)
    print('已全部下载完成')


# 解析页面
def getContent(address):
    response = requests.get(address)
    return BeautifulSoup(response.content, 'html.parser')


# 获取图片的地址
def getDownloadImgAddr(content):
    target_parent = content.find('dd', class_='p')
    target = target_parent.p.img
    img_address = target['src']
    return img_address


# 获取下一个页面的地址
def getNextAddr(content):
    index = content.find('a', class_='c')
    next_target = index.next_sibling
    if next_target is None:
        print('没有下一个了')
        return None
    else:
        print('下一个')
        next_addr = 'https://www.walltu.com' + next_target['href']
        print(next_addr)
        return next_addr


# 获取文件的名称
def getFileName(address):
    end_index = address.rfind('.')
    start_index = address.rfind('/')
    return address[start_index + 1:end_index]


# 下载图片
def downloadImg(imgAddress,fromAddr,dir,file_name):
    print('正在下载:'+file_name)
    img = open(dir+'/'+file_name+'.jpg', 'wb')
    headers = {
        'Referer': fromAddr
    }
    img_response = requests.get(imgAddress, headers=headers)
    img.write(img_response.content)
    img.close()
    print('下载完成:'+imgAddress)

# 调用,传入要下载的图片的地址
downloadAllImg('https://www.walltu.com/tuku/201801/212736.html')

代码到这里就完成了,想要下载其他组图,只要换一下,最后调用的地址就可以了。

最后,我们看一下效果吧。成功下载到了一整组图。

最后的效果

大家快去试一下吧!