利用Python3.5多线程抓取妹子图

很久之前就写了这个爬妹子图的Python爬虫,并且开源到Github上,然而居然没有人给小心心,虽然现在失效了,妹子图网站开启了反爬虫机制,但还是可以作为大家学习的样例,注释也很清晰。

0x01 起因

很多人一开始学习编程技术都是源于兴趣,小东当时看到某博主,居然用Python爬虫批量下载图片,当时就给我震惊了震惊

于是瞬间激发了我的学习兴趣,最后也就促成了这个GetMM爬虫项目。


0x02 分析

拿到妹子图的网站,先对网页结构进行了分析,然后使用python3.5版本,利用BeautifulSoup模块和urlib模块,同时实现了多线程抓取,下载,保存到对应文件夹的整个一个流程。

我知道你们看代码肯定很无聊,所以先放上一张成果图…


0x03 核心代码如下

#这是主文件 This is the main file!
#coding: utf-8
import argparse #argparse模块的作用是用于解析命令行参数
import urllib
import requests#urllib的request模块抓取URL内容
import os#系统啦
#看到以上的引入,所以你需要配置这些环境,urllib和BeautifulSoup,我觉得真的是很强大,有Urlib2更新了,学习一波吧!
#不得不说正则表达,给我带来的感觉是非常不错的,这也体现了程序的便利性!-技术改变生活-2017-04-27

from MuchThread import MuchThread #引入多线程
from urllib import request #引入request
from bs4 import BeautifulSoup #BeautifulSoup 4.x引入,beautifulsoup可以从HTML或XML文件中提取(解析)数据的Python库

#得到网页HTML的内容,源代码 get_html(URL)
def get_html(url_address):
    #构造请求头的客户端headers
    headers = {'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:23.0) Gecko/20100101 Firefox/23.0'}
    #urllib.request.Request(url,date,headers)
    #获取网页
    req = urllib.request.Request(url=url_address,headers=headers)
    return urllib.request.urlopen(req)

#小东留名!啊哈哈-www.dyboy.cn
#get_soup(html) 将urlib获取的html封装到bs中,方便文档处理,返回bs的一个实例
def get_soup(html):
    #判断是否成功获取了网页
    if html == None:
        return None
    #使用方法BeautifulSoup(markup, "html.parser")
    #html.read(),读取当前对象html文档的内容,html转化为unicode编码的,解析成xml
    return BeautifulSoup(html.read(), "html.parser")


#get_img_dirs(soup),获取所有相册标题&链接,参数为bs的一个实例,返回字典(key:标题,value:内容)
def get_img_dirs(soup):
    #判断对象是否存在
    if soup == None:
        return None
    #bs.find(tag,attributes,recursive,text,keywords)函数
    #bs.findAll(tag,attributes,recursive,text,limit,keywords)
    #find(id="pic")find(name,attrs,recursive,text,**wargs)
    '''这些参数相当于过滤器一样可以进行筛选处理,不同的参数过滤可以应用到以下情况:
    查找标签,基于name参数
    查找文本,基于text参数
    基于正则表达式的查找
    查找标签的属性,以及基于attrs参数
    基于函数的查找'''
    # soup.find(id="pins").findAll(name='li'),找到id="pins",下所有的li标签
    lis = soup.find(id="pins").findAll(name='li') # findAll(name='a') # attrs={'class':'lazy'}
    if lis != None:
        img_dirs = {};#新的存储相册目录字典
        for li in lis:
            links = li.find('a')#找到li标签下的最近一个a标签内容
            alt = links.find('img').attrs['alt']#在a标签内容中找到最近的img的属性alt的值
            site = links.attrs['href']#在a标签下的属性href值为相册地址
            img_dirs[alt] = site;#alt相册名字,site相册链接
        print(img_dirs)#输出获取到的相册字典
        return img_dirs#返回字典

#获取相册中的内页图片get_album_num(links, dir_soup),links:链接,get_soup:一个bs对象,返回图片总数
def get_album_num(links, dir_soup):
    ##############后期修改的地方#######################
    #找到links页面下属性class为pagenavi的div
    divs = dir_soup.findAll(name='div', attrs={'class':'pagenavi'})
    navi = divs[0]#有相同的取第一个
    code = navi['class']#code 为 div下class对应的值-pagenavi

    links2 = navi.findAll(name='a')
    if links2 == None:
        return None
    a = []
    url_list = []
    #循环获取div下的数字
    for link in links2:
        h = str(link['href'])#获取links2下的href的属性值-网址,转化为字符串便于使用str.replace(old, new[, max])
        n = h.replace(links+"/", "")#得到字符数字
        #异常处理,把int(n)加到a[]的末尾
        try:
            a.append(int(n))
        except Exception as e:
            print(e)

    _max = max(a)#获取a[]下最大的数字,即为最大页数
    for i in range(1, _max):
        u = str(links+"/"+str(i))#构造页面链接
        url_list.append(u)#添加到链接列表
    return url_list#返回链接列表

#获取相册下的图片download_img_from_page(name, page_url)
def download_img_from_page(name, page_url):
    dir_html = get_html(page_url)#获取网页HTML
    dir_soup = get_soup(dir_html)#封装到bs中

    # 得到当前页面的图片
    main_image = dir_soup.findAll(name='div', attrs={'class':'main-image'})#找到当前页面下的“main-image”对应的div
    if main_image != None:
        for image_parent in main_image:
            imgs = image_parent.findAll(name='img')#找到img标签的内容
            if imgs != None:
                img_url = str(imgs[0].attrs['src'])#找到第一个img下属性src的值,转换为字符串
                filename = img_url.split('/')[-1]#文件名为src的网址用split分割/,取最后一个作为文件名,避免重复
                print("开始下载:" + img_url + ", 保存为:"+filename)
                save_file(name, filename, img_url)#保存

#定义保存图片save_file()
def save_file(name, filename, img_url):
    print(img_url+"=========")
    img = requests.get(img_url)#获取图片
    name = str(name+"/"+filename)#文件夹名
    #with expresion as variable
    with open(name, "wb") as code:
        code.write(img.content)

#download_imgs(info):下载图片函数info为一个字典(相册列表)
def download_imgs(info):
    if info == None:
        return

    name = info[0]#album->alt
    links = info[1]#album->href
    if name == None or links == None:
        return None#获取内容失败
    print("正在创建相册:" + name +" " + links)
    #异常处理
    try:
        os.mkdir(name)#当前目录创建文件夹 还有makedirs(path[,mode]) mode默认0777
    except Exception as e:
        print("文件夹:"+name+",已经存在了,呱唧~")
    print("正在获取相册《" + name + "》内,图片的数量...")
    dir_html = get_html(links)#获取网页内容,返回HTML
    dir_soup = get_soup(dir_html)#编码HTML,封装到bs中
    img_page_url = get_album_num(links, dir_soup)#获取当前相册下的照片数量
    '''
    # 得到当前相册的封面,Just for a test!
    main_image = dir_soup.findAll(name='div', attrs={'class':'main-image'})
    if None != main_image:
        for image_parent in main_image:
        imgs = image_parent.findAll(name='img')
            if None != imgs:
                img_url = str(imgs[0].attrs['src'])
                filename = img_url.split('/')[-1]
                print("开始下载:" + img_url + ", 保存为:"+filename)
                save_file(t, filename, img_url)'''

    # 获取相册下的图片
    for photo_web_url in img_page_url:
        download_img_from_page(name, photo_web_url)

#main
if __name__ == '__main__':
    parser = argparse.ArgumentParser()#命令行下
    parser.add_argument("echo")
    # parser.add_argument("url")
    # url = int(args.url)
    args = parser.parse_args()
    url = str(args.echo)
    print("开始解析:" + url)

    html = get_html(url)#获取用户输入的链接的相册列表网页
    soup = get_soup(html)#封装至bs
    img_dirs = get_img_dirs(soup)#获取相册的名字及链接
    if img_dirs == None:
        print("呱唧!无法获取该网页下的相册内容...")
    else:
        for d in img_dirs:
            my_thread = MuchThread(download_imgs, (d, img_dirs.get(d)))
            my_thread.start()
            my_thread.join()

0x04 总结

其实python写爬虫真的很简单,之前在CSDN上发的文章很细节,同时在Github上有对应的源码,小东就不再赘述了,如果您有任何问题,欢迎交流!


项目开源地址:Github

发表评论 / Comment

用心评论~