为了账号安全,请及时绑定邮箱和手机立即绑定

爬虫入门实践-车模图片下载

标签:
Python

爱美之心人皆有之,去下载美女图片吧。
百度随便搜索一下:美女图,找到mm131.com网站。
以“性感车模”为例(http://www.mm131.com/chemo/),尝试编写一个下载图片的爬虫程序。

https://img1.sycdn.imooc.com//5d3076cc0001838a06000600.jpg

车模图片示例

结果:仅此“性感车模”一类图片达到惊人的16347张。
所以,实践中以切片选取部分来验证程序正确性,全部下载完太累,也太慢。


https://img1.sycdn.imooc.com//5d3076d500018e9806000600.jpg

图片数量太惊人,请以切片实验

一、车模页面链接(导航)的构建
“性感车模”地址入口:http://www.mm131.com/chemo/
点击第3页,查看底部车模页面链接源代码,可以发现:

<dd class="page">
    <a href="/chemo/" class="page-en">首页</a>
    <a href="list_3_2.html" class="page-en">上一页</a>
    <a href="/chemo/" class="page-en">1</a>
    <a href="list_3_2.html" class="page-en">2</a>
    <span class="page_now">3</span>
    <a href="list_3_4.html" class="page-en">4</a>
    <a href="list_3_5.html" class="page-en">5</a>
    <a href="list_3_6.html" class="page-en">6</a>
    <a href="list_3_7.html" class="page-en">7</a>
    <a href="list_3_8.html" class="page-en">8</a>
    <a href="list_3_9.html" class="page-en">9</a>
    <a href="list_3_10.html" class="page-en">10</a>
    <a href="list_3_4.html" class="page-en">下一页</a>
    <a href="list_3_10.html" class="page-en">末页</a></dd>

于是构建出导航地址的完整链接:
第1页:http://www.mm131.com/chemo/
第2页:http://www.mm131.com/chemo/list_3_2.html
第3页:http://www.mm131.com/chemo/list_3_3.html
……
最后一页(末页):http://www.mm131.com/chemo/list_3_10.html

代码片断如下:

def model_nav_links():
    page_urls = []
    page_urls.append('http://www.mm131.com/chemo/')    for page_index in range(2, 11):
        page_urls.append('http://www.mm131.com/chemo/list_3_' + str(page_index) + '.html')    return page_urls

思考:为什么是10?如果更多呢?如何以程序方式获取页面数?

https://img1.sycdn.imooc.com//5d3076dd000152c207030869.jpg

网站链接分析

二、页面上每位车模的链接入口
查看源代码发现,每位车模的链接入口并没有规律,类似http://www.mm131.com/chemo/****.html

<dd><a target="_blank" href="http://www.mm131.com/chemo/525.html"><img class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="http://img1.mm131.me/pic/525/m525.jpg" alt="美女车模小乔黑色吊带丝袜诱惑" width="120" height="160" />美女车模小乔黑色吊带丝</a></dd>

采取的处理方法:用BeautifulSoup库来解析页面,使用soup对象的select()获取每位车模的链接入口,以及对应的车模图片img标签的alt属性(图片替代文本)。
取此文字作图片存放文件夹名称使用。
代码片断如下:

page_soup = BeautifulSoup(page_html, 'html.parser')
page_a_links = page_soup.select('body > div.main > dl > dd a')for page_a_link in page_a_links:    # 有img标签的是车模
    if page_a_link.find('img'):
        page_model_hrefs.append(page_a_link.get('href'))
        page_model_texts.append(page_a_link.find('img').get('alt'))

https://img1.sycdn.imooc.com//5d3076e30001d6cd07580537.jpg

车模首页_页面总数_图片地址

三、某位车模的图片总页面数及图片页面地址构建
图片总页数用正则表达式 r'共(\d.*?)页'匹配获取。
图片页面地址则根据其规律,直接用url.replace('.html', '_'+str(i)+'.html')来实现。
这里的实现代码是不是有点眼熟?

代码片断如下:

def get_img_page_links(url):
    """构建某位车模的所有图片页面链接"""
    # url: 某位车模链接首页。返回:某位车模所有页面链接列表
    # 加入第1页
    img_pages = [url]    # 获取图片总页数img_page_total
    img_html = get_html(url).text
    img_soup = BeautifulSoup(img_html, 'html.parser')
    img_page_nums = img_soup.select('body > div.content > div.content-page > span')
    img_page_total = re.findall(r'共(\d.*?)页', img_page_nums[0].string)[0]    # 生成相应的图片页面地址
    for i in range(2, int(img_page_total)+1):        # 页面构建方式:
        # 第1页:http://www.mm131.com/chemo/1603.html
        # 第2页:http://www.mm131.com/chemo/1603_2.html
        img_href = url.replace('.html', '_'+str(i)+'.html')
        img_pages.append(img_href)    return img_pages

四、某位车模的某张图片地址
车模某张图片的选择器:
body > div.content > div.content-pic > a > img
运行后发现最后一位车模的最后一张图片会出错!
检查http://www.mm131.com/chemo/20_15.html发现:
其它图片点击会链接跳转到下一页图片,而最后一张图片并没有链接。
此时要改变选择器:body > div.content > div.content-pic > img
获取图片地址后,下载大法上演。

五、下载图片
比如获取一张图片地址:http://img1.mm131.me/pic/20/15.jpg
直接访问会出现403错误(资源不能访问)。
You do not have permission to get URL '/pic/20/15.jpg' from this server.
很显然,这是网站采取的反爬虫机制之一。

https://img1.sycdn.imooc.com//5d30770200011ed706000600.jpg

Chrome浏览器F12开发者工具

继续使用的Chrome浏览器F12开发者工具,选“Network”项,按F5刷新网页,此时会显示网络请求及响应信息。
找到图片15.jpg,会发现Request Headers部分有数据请求头要求。
重新梳理一下:
我们浏览的网页是:http://www.mm131.com/chemo/20_15.html,相对应headers部分的Referer
我们要的图片地址是:http://img1.mm131.me/pic/20/15.jpg,headers部分的Host对应图片地址中的域名。
构建请求头数据(字典),代码片断如下:

headers = {}
headers["Accept"] = "image/webp,image/*,*/*;q=0.8"headers["Accept-Encoding"] = "gzip, deflate, sdch"headers["Accept-Language"] = "zh-CN,zh;q=0.8,en;q=0.6"headers["Cache-Control"] = "no-cache"headers["Connection"] = "keep-alive"headers["Host"] = get_from_host(img_url)
headers["Pragma"] = "no-cache"headers["Referer"] = img_from_url
headers["User-Agent"] = "Mozilla/5.0 (Windows NT 6.1; WOW64)<省略……>"

至此,辛苦的分析完毕!

Talk is cheap. Show me the code --Linus

#!/usr/bin/env python# -*- coding: utf-8 -*-# @Time    : 2018-05-01 21:27# @Author  : AntsPi.com# @File    : car_show_model.pyimport osimport timeimport reimport requestsfrom bs4 import BeautifulSoupfrom urllib.parse import urlparsedef get_from_host(url):
    # 网址解析 from urllib.parse import urlparse
    urlParse = urlparse(url)    # print(urlParse.netloc)
    return urlParse.netlocdef get_html(url):
    try:
        headers = {"User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/"
                                "537.36 (KHTML, like Gecko) Chrome/"
                                "42.0.2311.90 Safari/537.36"}
        r = requests.get(url, headers=headers, timeout=30)
        r.raise_for_status()
        r.encoding = 'gb2312'
        return r    except Exception as e:
        print('Error: ', e)        returndef model_nav_links():
    page_urls = []
    page_urls.append('http://www.mm131.com/chemo/')    for page_index in range(2, 11):
        page_urls.append('http://www.mm131.com/chemo/list_3_' + str(page_index) + '.html')    return page_urlsdef get_img_page_links(url):
    """构建某位车模的所有图片页面链接"""
    # url: 某位车模链接首页。返回:某位车模所有页面链接列表
    # 加入第1页
    img_pages = [url]    # 获取总页数img_page_total
    img_html = get_html(url).text
    img_soup = BeautifulSoup(img_html, 'html.parser')
    img_page_nums = img_soup.select('body > div.content > div.content-page > span')
    img_page_total = re.findall(r'共(\d.*?)页', img_page_nums[0].string)[0]    # 生成相应的图片页面地址
    for i in range(2, int(img_page_total)+1):        # 页面构建方式:
        # 第1页:http://www.mm131.com/chemo/1603.html
        # 第2页:http://www.mm131.com/chemo/1603_2.html
        img_href = url.replace('.html', '_'+str(i)+'.html')
        img_pages.append(img_href)    return img_pagesdef download_img(url, dir_name):
    """下载1张图片"""
    # url: 车模页面的链接(页面里面含有1张图片)
    # dir_name: 下载后图片保存的文件夹名
    # 获取车模图片的地址
    img_html = get_html(url).text
    img_soup = BeautifulSoup(img_html, 'html.parser')
    img_href = img_soup.select('body > div.content > div.content-pic > a > img')    if img_href:
        img_url = img_href[0].get('src')    else:        # 最后一页是没有链接a标签的,要直接找img标签。
        img_href = img_soup.select('body > div.content > div.content-pic > img')
        img_url = img_href[0].get('src')    # 下载后的图片文件名
    only_file_name = os.path.basename(img_url)
    save_file_name = os.path.join(dir_name, only_file_name)    # 文件不存在 或 文件长度为0时,下载数据
    if not os.path.exists(save_file_name) or os.path.getsize(save_file_name) == 0:
        print('正在下载:{} ...'.format(save_file_name))
        get_img_data(url, img_url, save_file_name)
        time.sleep(0.5)    else:
        print('文件已存在:{} ...'.format(save_file_name))def get_img_data(img_from_url, img_url, file_name):
    """下载图片数据"""
    # img_from_url: 包含图片的页面链接地址
    # img_url: 图片的地址
    # file_name: 下载后的图片文件名
    # 构建请求头数据(字典)。不加会被网站拒绝(反爬虫)
    headers = {}
    headers["Accept"] = "image/webp,image/*,*/*;q=0.8"
    headers["Accept-Encoding"] = "gzip, deflate, sdch"
    headers["Accept-Language"] = "zh-CN,zh;q=0.8,en;q=0.6"
    headers["Cache-Control"] = "no-cache"
    headers["Connection"] = "keep-alive"
    headers["Host"] = get_from_host(img_url)
    headers["Pragma"] = "no-cache"
    headers["Referer"] = img_from_url
    headers["User-Agent"] = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36"
    try:
        r = requests.get(img_url, headers=headers, timeout=30)
        r.raise_for_status()        if r.status_code == 200:            with open(file_name, 'wb') as fw:
                fw.write(r.content)    except Exception as e:
        print('Error: ', e)        returnif __name__ == '__main__':    # 图片计数
    img_total_num = 0
    # 车模页面的链接
    page_links = model_nav_links()    # 页面上车模们的链接及对应说明文字
    page_model_hrefs = []
    page_model_texts = []    # 实验时只用了 1 页page_links[:1]
    for page_link in page_links[:1]:
        page_html = get_html(page_link).text
        page_soup = BeautifulSoup(page_html, 'html.parser')
        page_a_links = page_soup.select('body > div.main > dl > dd a')        for page_a_link in page_a_links:            # page_a_link 的类型是:class 'bs4.element.Tag
            # 有img标签的是车模
            if page_a_link.find('img'):
                page_model_hrefs.append(page_a_link.get('href'))
                page_model_texts.append(page_a_link.find('img').get('alt'))        # 图片存放总文件夹
        model_save_dir = 'model'
        model_save_dir = os.path.join(os.getcwd(), model_save_dir)        if not os.path.exists(model_save_dir):
            os.mkdir(model_save_dir)        # 网页链接索引编号及网页链接。实验时取3位车模[:3]
        for i, page in enumerate(page_model_hrefs[:3]):            # 以车模的文字作为文件夹名
            img_save_dir = os.path.join(model_save_dir, page_model_texts[i])            if not os.path.exists(img_save_dir):
                os.mkdir(img_save_dir)            # 单个车模的页面链接page(某位车模的首页链接)
            for img_page_link in get_img_page_links(page):
                img_total_num = img_total_num + 1
                print('No.{}'.format(str(img_total_num)))
                download_img(img_page_link, img_save_dir)

原创初稿:2018-05-03



作者:代码小工蚁
链接:https://www.jianshu.com/p/915eee3261c6


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消