美文网首页Spbeen——Python技术栈程序员
零基础玩转基础图片爬虫【文档教程】

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

作者: 布拉豆 | 来源:发表于2018-10-31 14:04 被阅读24次

    0. 写在前面

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

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

    1. 准备工作

    • 安装Python3

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

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

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

    测试下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,为啥呀?原因有下:

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

    • 正常图片的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

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

    • 导入部分

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

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

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

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

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

    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文件夹,慢慢下载哟

    相关文章

      网友评论

        本文标题:零基础玩转基础图片爬虫【文档教程】

        本文链接:https://www.haomeiwen.com/subject/mjkatqtx.html