Spbeen——Python技术栈程序员

零基础玩转基础图片爬虫【文档教程】

2018-10-31  本文已影响24人  布拉豆

0. 写在前面

  1. 代码和所需要库,以存放github,需要的自提。传送门:https://github.com/Vrolist/ScriptPython-ImageSpider

  2. 视频教程【免费】:https://study.163.com/course/courseMain.htm?courseId=1006148015

1. 准备工作

测试下python3以及库是否可用:

test_enviroment.PNG

2. 分析目标网站

目标网站:斗图啦,网址:www.doutula.com

本次文档主要是对斗图啦网站进行抓取,下载图片。

首先来分析下网站首页

web.PNG page.PNG

首页这里有不同的套图,底部有个翻页。点击不同的翻页,看下效果...

2.PNG 6.PNG

这里贴出了第二页和第六页的截图,url的规律是http://www.doutula.com/article/list/?page=页码

所以本次的目的,就是抓取该类url下的全部图片并保存本地

3. 获取单页的全部图片

这里以第二页的网页为例:

2-s.PNG

图片的右侧已经打开了调试工具,且当前是Elements栏,看到class="col-sm-9"class="col-sm-3",这个页面是基于Bootstrap搭建的,肯定跑不了。

然后需要下载的图片,全部在col-sm-9里面,所以定位所需的图片,就简单了,然后就有了下面这张图:

xpath-1.PNG

PS:在Elements栏打开xpaht检索框,按 ctrl+f 就会弹出框,支持字符串、selector、xpath三种检索方式。

这里的检索结果,只有一个,就是我们要的那个,太好了,接下来就开始获取图片的img标签,然后提取它的属性src即可。于是....


xpath-img.PNG

结果显示有50个图片,但是这个结果绝对是错的,因为我数了,没这么多。然后我就自席间擦,发现gif标志就是一个img的标签图,所以,这里的xpath需要做判断,只要大图,不要gif图。

于是,又有了下面这个图:

xpath-img2.PNG

xpath规则,由.//div[@class="col-sm-9"]//img/@src变成了.//div[@class="col-sm-9"]//img/@data-original,为啥呀?原因有下:

另外,特别重要的一点。有经验的应该知道,data-original这个属性的出现,肯定是该网页使用了jQuery图片延迟加载插件jQuery.lazyload,该插件的使用,就是依赖于data-original属性,并且该img标签,类名肯定有个值,叫lazy

到这,就正确的提取到了我们需要的40个图片【我数了,4*10,正确无误】,而且取出来的url,是包含了域名的,不需要做额外操作。

接下来就是写代码了,贴个截图,看下代码和效果:

import requests
from lxml import etree

url = 'http://www.doutula.com/article/list/?page=2'
headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36',
}

resp = requests.get(url,headers=headers)

html = etree.HTML(resp.text)
imgs = html.xpath('.//div[@class="col-sm-9"]//img/@data-original')
print(imgs,len(imgs))
py-1.PNG

运行结果一切正常,这里对代码做个简短的说明:

单页的图片地址提取,到这就完成了。后面,我们来做图片下载

4. 图片下载

前面我们拿到了图片的地址,有图片地址就可以直接请求图片并保存本地了,很简单。步骤如下:

逻辑是非常简单的,下载图片也是一个从url到本地文件的过程,那就来封装函数吧

先上代码:

def download_img(src):
    filename = src.split('/')[-1] # 步骤1
    img = requests.get(src, headers=headers) # 步骤2
    # img是图片响应,不能字符串解析;
    # img.content是图片的字节内容
    with open('imgs/' + filename, 'wb') as file: # 步骤3
        file.write(img.content) # 步骤4
    print(src, filename) # 步骤5

代码比较简洁,定义一个 download_img 函数,接收一个参数src,然后完成下载操作,这里也来介绍下:

  1. 步骤一,从传入的参数src里面,提取图片的名字。通常url经过 / 分割,最后一个字符串就是图片名。

  2. 对src发起请求,记住要带上请求头,就可以拿到响应,存入img。此时的img是http响应,响应数据是一张图片,响应的头部还有些数据。

  3. 步骤三是使用open函数,以二进制写的方式打开一个文件,然后写入img.content。为什么用二进制?因为img.content是字节。

  4. 另外,步骤三里面,有个imgs/,需要在当前py文件所在的目录中,创建一个imgs的文件夹,程序运行前创建好。

  5. 输出,其余的没啥

看下运行结果和图片文件截图:

40.PNG 40-2.PNG

重要说明:虽然这里没有碰到坑,但是图片请求和下载,一定会遇到一个Referer的坑。这个问题的来源,是云托管服务中,在存储图片时,不希望别人网站拿去盗用,所以就设置一个“防跨域请求”的限制。

在请求图片时,查看下请求头的Referer字段【浏览器请求时,会把域名放上去】。如果是来自未设置网站的来源,则不返回图片。所以请求图片,推荐在请求头中,加上Referer字段,值就是域名

当前的完整代码部分:

import requests
from lxml import etree

url = 'http://www.doutula.com/article/list/?page=2'
headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36',
    'Referer':'http://www.doutula.com/',
}
def download_img(src):
    filename = src.split('/')[-1]
    img = requests.get(src, headers=headers)
    # img是图片响应,不能字符串解析;
    # img.content是图片的字节内容
    with open('imgs/' + filename, 'wb') as file:
        file.write(img.content)
    print(src, filename)
resp = requests.get(url,headers=headers)

html = etree.HTML(resp.text)
imgs = html.xpath('.//div[@class="col-sm-9"]//img/@data-original')
for img in imgs:
    download_img(img)

5. 翻页处理

翻页这里,也不难,但是涉及些知识点,所以这里好好讲讲,多个方案对比看看。

第一种:函数递归

分析图片的url以及翻页,封装一个函数,在函数里面判断下一页,有则调用自身。代码会很简单,且思路清晰。

第二种:循环拼接URL

这种方式,适合有规则的url,找到规律,循环操作,并且可以预判最后一个url,方便停止。循环就是翻页的过程,简单。

第三种:函数返回URL

基于 【1 + 3】 思路,首先一个死循环,循环内调用函数,处理第一个url。函数在处理了图片url和翻页的时候,这时返回下一页的url。循环收到了函数的返回值,判断有没有下一页?有,继续调用函数;没有,停止循环。这样就不构成递归,而是简单的循环。

第四种:生成器

基于思路【3】,函数返回,return即可;如果你将return改成yield,就成了生成器,用法基本和思路【3】一致。

不考虑实际情况,再多思路都是扯淡,所以这里还是先上截图,看下斗图啦网站的翻页是怎么样的。

page-1.PNG page-2.PNG page-8.PNG page-587.PNG

上面贴了三张图,分别是 1 - 2 - 8 - 587 三页。

从图中可以看出,第二页没有 9 和 10 页,第八页有 9 和 10 、 11页。也就是说到了某一页,对应的前后三页的数据都是展示【除了没有的】。

然后看到第一页和最后一页,第一页中往前翻的箭头是无法点击的;最后一页中往后翻的箭头是无法点击的。

所以,有以下几个方案可以做:

  1. 获取翻页的最大数值,走思路【2】的方案,循环拼接URL

  2. 获取翻页的全部URL,逐个请求并分析下次所得的URL,做个筛查,请求过的URL不在请求。思路【4】

  3. 针对不是最后一页就有下一页翻页的思想,做函数的递归调用。思路【1】,总共587

  4. 针对不是最后一页就有下一页翻页的思想,做函数返回URL的方案。思路【3】

  5. 判断是否有图片,有图片则表示可能有下一页,继续请求,用图片来判断是否坐下一页的请求,思路【2】

  6. 等等,方法挺多的....

方法这么多,选一个简单且可行的,第5个方法。

为什么选第5个?因为:数字递增且URL容易拼接,每页都需要对图片进行解析,操作方便。循环+函数返回的操作,是Python的基本操作,要求低。

有思路有方法,那撸起袖子开始干了....

首先是封装函数函数,在解析图片的基础之上,判断图片的数据:如果有返回True;没有返回False;很简单,上函数代码:

def parse_page(url):
    resp = requests.get(url,headers=headers)
    html = etree.HTML(resp.text)
    imgs = html.xpath('.//div[@class="col-sm-9"]//img/@data-original')
    if imgs:
        return True
    else:
        return False

有这个函数,那下面就是写个循环逻辑,对该函数进行调用并一直判断函数返回值,再递增数字,拼接URL,在调用函数了,整体代码如下:

import requests
from lxml import etree
from time import sleep

url = 'http://www.doutula.com/article/list/?page=2'
headers = {
    'Referer':'http://www.doutula.com/',
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36',
}

def parse_page(url):
    resp = requests.get(url,headers=headers)
    html = etree.HTML(resp.text)
    imgs = html.xpath('.//div[@class="col-sm-9"]//img/@data-original')
    if imgs:
        return True
    else:
        return False


base_url = 'http://www.doutula.com/article/list/?page={}'
i = 1
next_link = True
while next_link:
    next_link = parse_page(base_url.format(i))
    if next_link :
        i += 1
    else:
        break
    print(i)
print('~OVER~')

代码中,首先定义几个值,用于拼接的base_url,循环用的i,以及下一页判断参数next_link。

在函数返回值为True是,i加1,同时while循环成立,继续调用函数;否则break,跳出循环。

测试结果:

145.PNG

怎么只有145页?因为145页是个错误页面,跳过就好,来看下145页的界面:

145page.PNG

所以代码稍微带动下,在i等于145 时再做个增加,跳过它。

不过,很快,我意识到了这是一个错误的思路。既然145页会错,后面还有400多页,肯定还有错误的页面,于是我就测试一下,错误页面有这些:

145,246, 250, 344, 470, 471, 563, 565, 589, 590, 591, 592

所以,在不知道哪些是错误页面的前提是,不能随便给数字做加法,然后就有了另一个思路:

所以,逻辑上就可以做连错处理。如果连续出现三次无页面,跳出循环,如果仅仅是一次、两次,跳过,继续往下爬。

上逻辑部分代码:

base_url = 'http://www.doutula.com/article/list/?page={}'
i = 1
error_time = 0
next_link = True
while next_link:
    next_link = parse_page(base_url.format(i))
    if next_link :
        i += 1
        error_time = 0
    else:
        if error_time>=3:
            print(error_time,'break')
            break
        i+=1
        error_time+=1
        next_link = True
    print(i,error_time)
print('~OVER~')

上运行结果截图【测试多次,结果会有差异】:

589.PNG

6. 总结

到这,翻页就完成了。加上前面的图片URL解析、图片下载、翻页处理,一个简单的图片爬虫就完成了。

不过呢,要加异常处理,否则一个错误终止整个程序,后续都没得玩了

贴下完成代码,以及运行结果图:

import requests
from lxml import etree
from time import sleep

url = 'http://www.doutula.com/article/list/?page=2'
headers = {
    'Referer':'http://www.doutula.com/',
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36',
}
def parse_page(url):
    resp = requests.get(url,headers=headers)
    html = etree.HTML(resp.text)
    imgs = html.xpath('.//div[@class="col-sm-9"]//img/@data-original')
    for img in imgs:
        try:
            download_img(img)
        except:
            pass
    if imgs:
        return True
    else:
        return False

def download_img(src):
    filename = src.split('/')[-1]
    img = requests.get(src, headers=headers)
    # img是图片响应,不能字符串解析;
    # img.content是图片的字节内容
    with open('imgs/' + filename, 'wb') as file:
        file.write(img.content)
    print(src, filename)

base_url = 'http://www.doutula.com/article/list/?page={}'
i = 1
error_time = 0
next_link = True
while next_link:
    sleep(0.5)
    try:
        next_link = parse_page(base_url.format(i))
    except:
        next_link = True
    if next_link :
        i += 1
        error_time = 0
    else:
        if error_time>=3:
            print(error_time,'break')
            break
        i+=1
        error_time+=1
        next_link = True
    print(i,error_time)
print('~OVER~')

截图是,下载15926张图片,但是程序依旧在运行,也就是说....图片还更多。


15926.PNG

你们拿到爬虫代码后,可以自己去运行,记得创建一个imgs文件夹,慢慢下载哟

上一篇下一篇

猜你喜欢

热点阅读