煎蛋网的图片爬虫

从学习Python的第八天就想要写个简单爬虫,当天就实现了功能,不过遇到了点问题,之后两天也都一一解决了。初学编程语言时写个小作品是令人兴奋的,这对学习过程来说是个很大的激励,所以我向来遵循Learning-by-doing。Learning-by-doing这容易联想到古典经济学的一个名词,被国人简单粗暴的翻译为“干中学”,意为在实践中学习。

2017.10.18:更新

无意中想到这个在初学 Python 时写的小玩具,代码很乱并且复用性很差,所以想到把这个小玩具重写一遍,虽然意义不大,写代码本身的乐趣已经远远超过代码能做的事情。把整个下载文件的爬虫分割成了几个部分:

def MKdir(dirname):
    if not os.path.isdir(dirname):
        os.mkdir(dirname)
    return os.path.abspath(dirname)

创建了 MKdir() 函数来创建一个文件夹, os.path.isdir() 方法可以检测文件夹是否存在,如果存在则返回 True ,反之则返回 False 。MKdir() 函数可以检测文件夹是否存在,如果不存在则新建一个,然后返回文件夹路径。

def Get_res(url):
        headers = {
	'Referer':'http://jandan.net/ooxx',
	'User-Agent': 'User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36'
	}
        try:
                Re = requests.get(url,headers=headers,timeout=6)
        except Timeout as e:
                print('请求超时:',e)
        except ConnectionError as e:
                print('请检查网络连接:',e)
        except HTTPError as e:
                print('网页错误:',e)
        return Re

返回一个 requests 的实例 Re,由于错误处理的部分还是比较长的,所以拆成了一个函数~

def Download_Img(url,name):
        if os.path.exists(name):
                exist_name = os.path.split(name)[1]
                print('{0}文件已经存在!'.format(exist_name))
        res = Get_res(url)
        with open(name,'wb') as f:
                for chunk in res.iter_content(100000):
                        f.write(chunk)

下载图片的函数,检测文件是否存在,如果不存在使用 open() 以二进制写入方式打开一个文件,使用 res.iter_content(100000) 迭代器每次返回写入 100000 字节的内容写入文件。 在这里有些人大概会有疑问,为什么要使用 res.iter_content() 迭代器,而不是使用 res.content() 一次性下载写入内容,这是为了避免下载大文件时占用掉所有内存,所以每次迭代的写入 10万 字节是一个好主意。

def Get_img_list(url):
        Re = Get_res(url)
        page = BeautifulSoup(Re.text,"lxml")      
        img_url_list = [ ]
        for i in page.ol.find_all('img'):
                img_url_list.append(i.attrs['src'])
        return img_url_list

这是作为爬虫本身的核心代码,通过分析网页源码可以发现,煎蛋网爬虫的代码都在 ol 标签块下,使用 BeautifulSoup 获取 ol 标签中所有带有 img 标签的链接,并且加入到 img_url_list 列表中。

def new_url(url):
        Re = Get_res(url)
        page = BeautifulSoup(Re.text,"lxml")
        new_url = page.find("a",{"class":"previous-comment-page"}).attrs['href']
        return 'http:'+new_url

在查看煎蛋网的页面源码时可以发现,上一页按钮的链接都含有一个属性 {“class”:”previous-comment-page”} ,那就好办了,连正则表达式都不需要了,直接用美味的汤(BeautifulSoup)就直接扣出来了。由于网站引用的图片是新浪图片,并且以相对链接形式 “//” 开头,而不是 ‘http://’ ,所以 http 这部分就需要自己合成了。

在分割图片链接的文件名时,原本是使用的正则表达式,后来灵感一闪,想到了 os.path.split() 模块不就是干分割文件名的嘛,其本身就只是操作 str 数据类型的文本,所以尝试了一下果然可以。

os.path.split('https://yearliny.com/favicon.ico')

在IDLE下运行,可以正常返回一个包含两个数据的元组,完美分割为:

("https://yearliny.com/","favicon.ico")

最后附上完整源码:

# -*- coding: utf-8 -*-
import os
import lxml
import requests
from bs4 import BeautifulSoup

# check dir exist,if don't creat a new
def MKdir(dirname):
        if not os.path.isdir(dirname):
                os.mkdir(dirname)
        return os.path.abspath(dirname)
        
def Get_res(url):
        headers = {
	'Referer':'http://jandan.net/ooxx',
	'User-Agent': 'User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36'
	}
        try:
                Re = requests.get(url,headers=headers,timeout=6)
        except Timeout as e:
                print('请求超时:',e)
        except ConnectionError as e:
                print('请检查网络连接',e)
        except HTTPError as e:
                print('网页错误:',e)
        return Re

# Download_Img(url,name),download a file,name contain path
def Download_Img(url,name):
        if os.path.exists(name):
                exist_name = os.path.split(name)[1]
                print('{0}文件已经存在!'.format(exist_name))
        res = Get_res(url)
        with open(name,'wb') as f:
                for chunk in res.iter_content(100000):
                        f.write(chunk)
                        
# return a list of image
def Get_img_list(url):
        Re = Get_res(url)
        page = BeautifulSoup(Re.text,"lxml")      
        img_url_list = [ ]
        for i in page.ol.find_all('img'):
                img_url_list.append(i.attrs['src'])
        return img_url_list

def new_url(url):
        Re = Get_res(url)
        page = BeautifulSoup(Re.text,"lxml")
        new_url = page.find("a",{"class":"previous-comment-page"}).attrs['href']
        return 'http:'+new_url

def main():
        page_num = int(input("How many pages you want to download?(About 8 images per page):"))
        page_url = "http://jiandan.net/ooxx"
        img_num = 0
        path = MKdir('jiandan')
        for n in range(page_num):
                img_list = Get_img_list(page_url)
                for i in img_list:
                        img_num += 1
                        img_url = 'http:' + i
                        img_name = os.path.split(img_url)[1]
                        img_path = os.path.join(path,img_name)
                        Download_Img(img_url,img_path)
                        print('{0} Downloading >> {1}'.format(img_num,img_name))
                page_url = new_url(page_url)
        print('Thanks you!All done!You have downloaded %d images.' % img_num)
if __name__ == '__main__':
        main()

这是一个非常简单的爬虫,在此发表一来作为学习记录,二来可以给和我一样的初学者提供一些参考。写爬虫的过程中,遇到的第一个问题就是爬6页就会被403禁止了。当我写好爬虫运行,一张张图片稳妥的下载到了硬盘中,于是准备泡一杯茶优雅的看着爬虫输出一条条胜利的消息,结果回来后看到了爆出的红字错误。 于是就想到了模拟UA,在了解这方面的使用方法时知道了Requests这个模块,于是就好好的了解了一下requests模块,看了官方文档后果然入其所说的那般简单优雅,API的易用性胜于urllib,修改headers只需要一行:

requests.get(url,headers = {'User-Agent': 'User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36'})

不知道煎蛋网检测机器人是根据什么特征判断的,所以把本机浏览器的headers全部修改成自己浏览器的header,查看本机headers就需要用到chrome的开发者工具(chrome-devtools):F12打开开发者工具并切换到Network标签,访问任意网站,查看任意一个GET请求,就能够知道自己浏览器的headers了。

最后附上源码。(注:需要安装所需模块,否则无法正常运行,模块安装不成功看这里

pip install BeautifulSoup4 requests lxml
from bs4 import BeautifulSoup
from urllib.request import urlretrieve
import requests
import lxml
import re
page_num = int(input("How many pages you want to download?(1~99):"))
headers = {
	'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
	'Accept-Encoding': 'gzip, deflate, sdch',
	'Accept-Language':'en-US,en;q=0.8,zh-CN;q=0.6,zh;q=0.4',
	'Connection': 'keep-alive',
	'Referer':'http://jandan.net/ooxx',
	'User-Agent': 'User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36',
	'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.3'
	}

page = BeautifulSoup(requests.get("http://jiandan.net/ooxx",headers = headers).text,"lxml")
img_num = 0

def get_img():
        global img_num
        for i in page.findAll("a",{"class":"view_img_link"}):
                img_url = ("http:"+i.attrs['href'])
                img_name = re.search(r"\w*.(jpg|gif|png)",img_url).group(0)
                urlretrieve(img_url,img_name)
                img_num += 1
                print("Downloading..."+ img_name)

def new_url():
    new_url = BeautifulSoup(requests.get(page.find("a",{"class":"previous-comment-page"}).attrs['href'],headers = headers).text,"lxml")
    return new_url

for n in range(page_num):
        get_img()
        page = new_url()
print('Thanks you!All done!You have downloaded %d images.' % img_num)

评论

《 “煎蛋网的图片爬虫” 》 有 8 条评论

  1. 沙发一个,喜欢就多学习,这个学习是一个享受的过程。

    1. 先生所言极是,学Python确实很有趣,各种完善的库,换个领域就是个全新的玩法啊

  2. 煎蛋网老大发过帖子说很不喜欢爬虫,最好别公开了

    1. 煎蛋网的图片都是外链的新浪微博,所以实际上这个图片爬虫对网站造成的负载不高。估计也没人用爬虫一直下载上面的图片,这个小爬虫没多少实际用途,只是练手用,煎蛋老大应该不会怪罪我的

  3. 好羡慕会写爬虫的,自己手动保存alpha.wallhaven.cc好久了……手都残了!

    1. 大多数情况下,仅仅是为了保存网页上图片的话,去写爬虫下载是划不来的,因为有很多现成的网页图片的批量下载工具,你可以找找看,只是为了下载图片没那么麻烦。我这里只是为了练手,所以才说这个爬虫实用价值不大。

  4. 下那么多图片,小心暴机

    1. 哈哈~实际上我真没下载多少,只是自己弄着玩~

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注