爱美之心人皆有之,去下载美女图片吧。
百度随便搜索一下:美女图,找到mm131.com网站。
以“性感车模”为例(http://www.mm131.com/chemo/
),尝试编写一个下载图片的爬虫程序。
车模图片示例
结果:仅此“性感车模”一类图片达到惊人的16347张。
所以,实践中以切片选取部分来验证程序正确性,全部下载完太累,也太慢。
图片数量太惊人,请以切片实验
一、车模页面链接(导航)的构建
“性感车模”地址入口: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?如果更多呢?如何以程序方式获取页面数?
网站链接分析
二、页面上每位车模的链接入口
查看源代码发现,每位车模的链接入口并没有规律,类似http://www.mm131.com/chemo/****.html
。
<dd><a target="_blank" href="http://www.mm131.com/chemo/525.html">
<img src="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'))
车模首页_页面总数_图片地址
三、某位车模的图片总页面数及图片页面地址构建
图片总页数用正则表达式 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.
很显然,这是网站采取的反爬虫机制之一。
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.py
import os
import time
import re
import requests
from bs4 import BeautifulSoup
from urllib.parse import urlparse
def get_from_host(url):
# 网址解析 from urllib.parse import urlparse
urlParse = urlparse(url)
# print(urlParse.netloc)
return urlParse.netloc
def 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)
return
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
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
def 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)
return
if __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