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

论坛 期权论坛 区块链     
布拉豆   2018-11-21 23:03   4203   0
            [h1]0. 写在前面[/h1][h1]1. 准备工作[/h1]


  • 安装Python3


  • 安装requests库,命令行 pip3 install requests


  • 安装lxml【需要解析xpath】,命令行 pip3 install lxml


  • 一点xpath基础,参考教程:《xpath教程》: 伊始


测试下python3以及库是否可用:
test_enviroment.PNG[h1]2. 分析目标网站[/h1]

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

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

首先来分析下网站首页
web.PNG
page.PNG

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

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

所以本次的目的,就是抓取该类url下的全部图片并保存本地[h1]3. 获取单页的全部图片[/h1]

这里以第二页的网页为例:
2-s.PNG

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

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

xpath-1.PNG



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

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

xpath-img.PNG

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

于是,又有了下面这个图:
xpath-img2.PNG

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


  • gif 的 img 标签,没有 data-original 属性


  • 正常图片的img和data-original属性,它们的值是完全一样的


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

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

接下来就是写代码了,贴个截图,看下代码和效果:
  1. import requestsfrom lxml import etreeurl = '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

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


  • 导入部分


  • headers,这个是最好加的,请求一个网站,请求头里面的User-Agent是浏览器信息,有这个就是模拟浏览器发出信息了

    1. html = etree.HTML(resp.text)
    复制代码
    解析纯html字符串,解析之后就可以使用xpath了

    1. imgs = html.xpath('.//div[@class="col-sm-9"]//img/@data-original')
    复制代码
    使用规则提取指定的数据,结果是列表格式;如果没有数据就是空列表;


  • 最后将数据输出,40个,正常。


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

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


  • 首先,通过图片地址,发起请求,拿到响应,响应的数据就是图片了


  • 然后,新建一个本地文件,将响应数据写到文件上


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

先上代码:
  1. 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,然后完成下载操作,这里也来介绍下:


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


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


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


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


  • 输出,其余的没啥


看下运行结果和图片文件截图:
40.PNG
40-2.PNG

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

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

当前的完整代码部分:
  1. import requestsfrom lxml import etreeurl = '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)
复制代码
[h1]5. 翻页处理[/h1]

翻页这里,也不难,但是涉及些知识点,所以这里好好讲讲,多个方案对比看看。[h3]第一种:函数递归[/h3]

分析图片的url以及翻页,封装一个函数,在函数里面判断下一页,有则调用自身。代码会很简单,且思路清晰。[h3]第二种:循环拼接URL[/h3]

这种方式,适合有规则的url,找到规律,循环操作,并且可以预判最后一个url,方便停止。循环就是翻页的过程,简单。[h3]第三种:函数返回URL[/h3]

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

基于思路【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页。也就是说到了某一页,对应的前后三页的数据都是展示【除了没有的】。

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

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


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


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


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


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


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


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


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

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

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

首先是封装函数函数,在解析图片的基础之上,判断图片的数据:如果有返回True;没有返回False;很简单,上函数代码:
  1. 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,在调用函数了,整体代码如下:
  1. import requestsfrom lxml import etreefrom time import sleepurl = '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 Falsebase_url = 'http://www.doutula.com/article/list/?page={}'i = 1next_link = Truewhile 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多页,肯定还有错误的页面,于是我就测试一下,错误页面有这些:
  1. 145,246, 250, 344, 470, 471, 563, 565, 589, 590, 591, 592
复制代码

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


  • 错误的数字,不会连着出现,最多出现一次


  • 末尾的几页,不会报错,直接出现无效,也就是末尾会出现连续的无图片


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

上逻辑部分代码:
  1. base_url = 'http://www.doutula.com/article/list/?page={}'i = 1error_time = 0next_link = Truewhile 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[h1]6. 总结[/h1]

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

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

贴下完成代码,以及运行结果图:
  1. import requestsfrom lxml import etreefrom time import sleepurl = '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 Falsedef 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 = 1error_time = 0next_link = Truewhile 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文件夹,慢慢下载哟         
分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:
帖子:
精华:
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP