[福利向]Python妹子图爬虫

作者: 3inchtime | 来源:发表于2018-04-09 23:01 被阅读3618次
    项目地址 https://github.com/3inchtime/CX_spiders

    作为Python的初学者,爬虫肯定是入门的不二选择,既能熟悉语法,又能通过爬虫了解一定的网络编程知识。

    要想完美的食用本篇教程,首先你需要熟悉Python的基础语法以及基础的数据结构,之后最好了解Python面向对象编程,还有xpath的基本语法。
    关于Python3的教程我十分推荐廖雪峰老师的Python教程
    https://www.liaoxuefeng.com/
    xpath非常简单,完全可以一边实践一边学习
    http://www.w3school.com.cn/xpath/index.asp

    新手司机上路,请注意!!!

    爬虫的原理其实很简单,模仿人浏览网页并记录数据。
    我们的目标网站——www.mmjpg.com

    如果你现在已经打开了这个网站,求求你们,把持住自己!!!


    现在,让我们踩下油门,开始飙车!!!

    我们的目的是保存每个妹子的图片,并以文件夹的形式保存在我们的电脑上。

    首先我们分析这个网站的url,这是很重要的一步

    打开首页 www.mmjpg.com 点击下一页,它的第二页是http://www.mmjpg.com/home/2,这时我们把2改成1,是不是就可以跳转到第一页呢?

    哇!!居然是404!!!

    所以我们现在了解到,这个网站第一页为www.mmjpg.com,后面的页面为http://www.mmjpg.com/home/n ,n是页码。
    目前,最后一页为http://www.mmjpg.com/home/88,所以我们最多能爬取到88页妹子图,看来这个网站目前只更新了这么多,按照每页15个妹子,那么......

    所以,我采取的策略是这样的:

    1. 首先应该输入我们需要爬取到多少页。(例如20页,那么我们就爬取1-20页。)
    2. 我们依次爬取这些页面上所有妹子的图片页面。(例如第1页有15个妹子,那么我们就把这15个妹子的url爬下来。)
    3. 最后我们进入妹子的页面,依次爬取下妹子的图片,并保存在本地。

    当然了,这肯定不是最优的爬取策略,如果你有更好的策略,请在评论区留言。

    我的开发环境为 Ubuntu 16.04 LTS 以及 Python3.6.4,理论上是兼容Windows和Mac OS的。

    由于我们的爬虫并不是很复杂,所以并不需要使用任何爬虫框架,我们主要使用的是Requests库

    Requests库十分强大,Requests 是使用 Apache2 Licensed 许可证的 基于Python开发的HTTP 库,其在Python内置模块的基础上进行了高度的封装,从而使得进行网络请求时,使用Requests可以轻而易举的完成浏览器可有的任何操作。


    现在正式开始代码时间

    既然我们要基于面向对象的思想写这个爬虫,那么我们就需要把这个爬虫写成一个类。
    首先
    pip install requests
    pip install lxml

    # -*- coding: utf-8 -*-
    import requests
    from lxml import etree
    
    class Spider(object):
        def __init__(self, page_num):
            self.page_num = page_num
            self.page_urls = ['http://www.mmjpg.com/']
            self.girl_urls = []
            self.girl_name = ''
            self.pic_urls = []
    
        def get_girl_urls(self):
            pass
    
        def get_pic_urls(self):
            pass
    
        def download_pic(self):
            pass
    
    if __name__ == '__main__':
        pass          
    

    我们使用lxml中的etree操作xpath,从而获取我要想要爬取的字段。

    想要实例化我们的爬虫类,page_num是必须输入的参数,所以用户必须在实例化这个类时输入页码。

    因为这个网站的第1页与后面的网页url没有规律,而且用户至少会爬取第1页,所以我们先将首页放入page_urls中,之后调用get_page_urls()获取所有想要爬取的url。

    根据我们的爬虫策略,先调用get_girl_urls()抓取所有妹子的url,将它们保存到girl_urls这个list中,之后将girl_urls传递给get_pic_urls(),获取所有图片的url保存到pic_urls,最后调用download_pic()下载图片。

    最后的main函数则用来让用户输入想要爬取的页码以及实例化Spider类。

    所以,首先我们先来抓取所有妹子的url
    在get_page_urls()中我们首先判断输入的页码(这里我们就不做输入负数的判断了),在获取到page_num后拼接出所有要爬取的页面url

        def get_page_urls(self):
            if int(page_num) > 1:
                for n in range(2, int(page_num)+1):
                    page_url = 'http://www.mmjpg.com/home/' + str(n)
                    self.page_urls.append(page_url)
            elif int(page_num) == 1:
                pass
    

    这样page_urls中就存放了我们所有要爬取的页面url了。

    打开首页,调出开发者工具



    我们定位到妹子的url,我们可以很容易的写出妹子url的xpath。

    我们使用Requests的get()方法,获取到这一页的HTML页面,并调用etree.HTML()将html转化成可以用xpath定位元素的格式。

    妹子的url的xpath很容易就能标记出来,它是class="title"的span标签下的a标签的href属性。

        def get_girl_urls(self):
            for page_url in self.page_urls:
                html = requests.get(page_url).content
                selector = etree.HTML(html)
                self.girl_urls += (selector.xpath('//span[@class="title"]/a/@href'))
    

    这样我们就获取到了我们要抓取的所有页面中的妹子url。


    接下来我们就要获取每张图片的url了。
    点进一个妹子的页面,我们发现每一页只有一张图片,页面的最下方有翻页,全部图片,下一张。



    按照常规的方法,我们只要获取一个妹子的图片总数,一般这种图片的url都是很有规律的,我们就可以拼接出所有图片的url,因为一般的图片链接都是有规律的。

    调出开发者工具(注意力不要放在妹子上!!!)。


    咦???这个图片的url怎么看起来很奇怪???往下点了几页,发现所有的url毫无规律,看来我们遇到了一个小小的麻烦,这样我们就不能通过普通的url拼接来获取图片的链接了,那我们现在该怎么办呢?

    我个人有两种解决办法:

    1. 一页一页的获取图片的url,我们可以获取第一页上的图片之后,进入下一页爬取下一页的图片,以此类推,直到爬下这个妹子所有的图片。
      这个办法是一种很常规的做法,很容易实现,但是不够优雅。
    2. 我们发现每一页都有一个按钮 “全部图片” ,我们点击全部图片之后,我们就看到了所有图片的url。



      这样我们就得到了所有图片的url,那么问题来了,爬虫本身并不具备点击这个按钮的功能,但是要想获取所有图片的url,我们必须点击这个按钮之后才能得到,怎么办呢?

    这时,我就要向你们介绍一大神器——Selenium

    Selenium 是为了测试而出生的. 但是没想到到了爬虫的年代, 它摇身一变, 变成了爬虫的好工具。
    Seleninm: 它能控制你的浏览器, 有模有样地学人类”看”网页

    Selenium可以操纵浏览器浏览网页,而且它可以点击按钮,甚至输入文字,是不是很神奇?
    Selenium可以操纵的浏览器有很多,我选择了比较常用的Chrome。

    pip install selenium
    

    之后下载你需要操纵的浏览器对应的驱动。
    https://www.seleniumhq.org/download/


    下载你需要的驱动,到你常用的目录就可以啦!


    下面我们使用Selenium来模拟点击“全部图片”的按钮,来获取所有图片的url了。

    import time
    from selenium import webdriver
    
        def get_pic_urls(self):
            driver = webdriver.Chrome('你的webdriver路径')
            for girl_url in self.girl_urls:
                driver.get(girl_url)
                time.sleep(3)
                driver.find_element_by_xpath('//em[@class="ch all"]').click()
    

    这里首先要实例化一个Chromedriver,webdriver的路径一定要精确到可执行文件。

    我们先使用webdriver的get()方法请求到页面,之后使用find_element_by_xpath()方法,用xpath标记到需要点击的按钮,之后调用click()方法点击这个按钮,这样就完成了我们控制浏览器获取所有图片的过程啦!

    如果一切正常的话,这时当你运行程序时,会有一个受程序控制的浏览器打开,并在三分钟后,“全部图片”的按钮会被点击,你就看到了除第一张以外其他的图片。

    这时我们调出开发者工具,我们就看到了所有图片的url了。



    现在我们就可以获得每张图片的url了,顺便获取一下妹子的标题。

        def get_pic_urls(self):
            driver = webdriver.Chrome('/home/chen/WorkSpace/tools/chromedriver')
            for girl_url in self.girl_urls:
                driver.get(girl_url)
                time.sleep(3)
                driver.find_element_by_xpath('//em[@class="ch all"]').click()
                time.sleep(3)
                # 这里暂停3秒之后获取html的源代码
                html = driver.page_source
                selector = etree.HTML(html)
                self.girl_name = selector.xpath('//div[@class="article"]/h2/text()')[0]
                self.pic_urls = selector.xpath('//div[@id="content"]/img/@data-img')
    

    在使用Selenium模拟点击之后,一定要使用time.sleep()暂停几秒,我们需要给页面中的JS代码时间,加载出图片的url,如果你不暂停几秒的话,除非JS的加载速度能快过我们代码的执行速度,不然我们是得不到图片的url的。

    在每次测试程序的时候总会有我们调用的Chrome出现,为了防止妹子干扰我们干正事(咳咳咳......),我们可以使用不加载图片的Chrome来爬取数据,当然了,我们也可以隐藏Chrome。

    chrome_options = webdriver.ChromeOptions()
    prefs = {"profile.managed_default_content_settings.images": 2}
    chrome_options.add_experimental_option("prefs", prefs)
    chrome_options.add_argument("--headless")
    chrome_path = '/home/chen/WorkSpace/tools/chromedriver'
    
        def get_pic_urls(self):
            driver = webdriver.Chrome(chrome_path, chrome_options=chrome_options)
                                         .
                                         .
                                         .
    

    --headless是无界面Chrome的选项
    prefs = {"profile.managed_default_content_settings.images": 2}是让Chrome不加载图片,因为我们并不需要让图片加载出来,我们只需要获取url就足够了,这样可以减少响应的时间。
    那么,你肯定会问我,既然可以使用无界面的Chrome,为啥还需要添加不加载图片的选项呢,实际上无界面的Chrome也是会加载图片的,只是你看不到罢了。
    这样就再也不会有妹子来打扰我们撸代码了......

    紧接着就调用我们的下载图片的方法。

    import os
    
    PICTURES_PATH = os.path.join(os.getcwd(), 'pictures/')
    
        def download_pic(self):
            try:
                os.mkdir(PICTURES_PATH)
            except:
                pass
            girl_path = PICTURES_PATH + self.girl_name
            try:
                os.mkdir(girl_path)
            except Exception as e:
                print("{}已存在".format(self.girl_name))
            img_name = 0
            for pic_url in self.pic_urls:
                img_name += 1
                img_data = requests.get(pic_url)
                pic_path = girl_path + '/' + str(img_name)+'.jpg'
                if os.path.isfile(pic_path):
                    print("{}第{}张已存在".format(self.girl_name, img_name))
                    pass
                else:
                    with open(pic_path, 'wb')as f:
                        f.write(img_data.content)
                        print("正在保存{}第{}张".format(self.girl_name, img_name))
                        f.close()
            return
    

    这里先设置好图片储存的路径,在当前目录下的pictures中,当然了,为了体验更友好,我也写了判断图片和图片文件夹是不是已经存在的方法,已经存在的图片就会跳过,这样我们就可以多次使用爬虫而不怕已经存在文件而报错了,最后使用os.write()方法就可以保存图片了。

    到这里我们就下载到了图片,赶快打开欣赏一番吧!!!

    WHAT THE FUCK !!??!!??

    看来我们遇到了这个网站的反爬虫,很多网站都可以识别爬虫,尤其是我们这种很初级的爬虫,一抓一个准。那我们怎么办才能避免被反爬虫识别出来呢?

    这其实是一门很深的学问,反爬虫与反反爬虫的斗争由来已久,但是这点问题还是难不倒我们的。

    我之前说过,爬虫就是模拟人浏览网页来获取数据,那么既然不想被反爬虫识别出来,那么我们的爬虫就要更像人一样浏览网页,那么我们现阶段究竟离模拟人浏览网页差多远呢???

    我们只需要把我们的爬虫伪装成浏览器就可以了。

    难道我们要使用Selenium控制Chrome来爬取网页么?

    不,完全不需要!这就需要我们有些计算机网络的基础知识了,我们浏览网页,绝大多数情况下都是基于HTTP协议的,在HTTP协议中,我们每次浏览网页都要发送一个Headers请求头,在请求头中包含了很多重要的东西。


    现在我们就来对比一下我们正常访问该网站的妹子图片和直接看妹子图片的请求头有什么不同。


    这是我们通过正常浏览网页,第一张图片的请求头。现在我们把第一张图片的url复制下来,重新打开浏览器,输入url,看看会发生什么?
    相信你已经看到了,出现在我们眼前的就是刚才让我们失望的那张图片了,现在我们来看看它的请求头是什么?

    在最下面一栏Request Headers中,我们发现返回失败的那张图片参数明显要多出很多,但是这都不是关键,关键就在于它少了什么?

    我相信通过对比你已经发现了端倪,那就是Referer

    Referer是用来判断这个HTTP请求是从哪个网站跳转过来的,当我们正常访问这个网站时,HTTP请求中的Referer都是'http://www.mmjpg.com/mm/xxxx',包含了这个Referer属性的请求就可以正常浏览图片,反之,当我们直接访问这个图片的链接,我们的HTTP请求中不包含Referer,那么服务器就会判断我们并不是在访问他们的网站,就将我们识别为爬虫了,从而返回了那张不是我们期望的那张图片。

    现在,既然我们已经分析出了原因,那么我们要怎么样才能解决这个问题呢?
    别担心,Request库早都为我们提供了解决办法,当我们使用Requests时,不只能添加url进去同时也可以加入headers。

    headers = {
        'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) '
                      'Chrome/65.0.3325.181 Safari/537.36',
        'Referer': "http://www.mmjpg.com"
    }
    

    我们只要在requests.get()中加入headers就可以了。

    img_data = requests.get(pic_url, headers=headers)
    

    现在,我们再次运行我们的爬虫!!!



    成功!!!
    接下来放上全部代码

    # -*- coding: utf-8 -*-
    import os
    import requests
    from lxml import etree
    import time
    
    from selenium import webdriver
    
    # 将Chrome设置成不加载图片的无界面运行状态
    chrome_options = webdriver.ChromeOptions()
    prefs = {"profile.managed_default_content_settings.images": 2}
    chrome_options.add_experimental_option("prefs", prefs)
    chrome_options.add_argument("--headless")
    chrome_path = '/home/chen/WorkSpace/tools/chromedriver'
    
    # 设置图片存储路径
    PICTURES_PATH = os.path.join(os.getcwd(), 'pictures/')
    
    # 设置headers
    headers = {
        'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) '
                      'Chrome/65.0.3325.181 Safari/537.36',
        'Referer': "http://www.mmjpg.com"
    }
    
    
    class Spider(object):
        def __init__(self, page_num):
            self.page_num = page_num
            self.page_urls = ['http://www.mmjpg.com/']
            self.girl_urls = []
            self.girl_name = ''
            self.pic_urls = []
    
    # 获取页面url的方法
        def get_page_urls(self):
            if int(page_num) > 1:
                for n in range(2, int(page_num)+1):
                    page_url = 'http://www.mmjpg.com/home/' + str(n)
                    self.page_urls.append(page_url)
            elif int(page_num) == 1:
                pass
    
    # 获取妹子的url的方法
        def get_girl_urls(self):
            for page_url in self.page_urls:
                html = requests.get(page_url).content
                selector = etree.HTML(html)
                self.girl_urls += (selector.xpath('//span[@class="title"]/a/@href'))
    
    # 获取图片的url的方法
        def get_pic_urls(self):
            driver = webdriver.Chrome(chrome_path, chrome_options=chrome_options)
            for girl_url in self.girl_urls:
                driver.get(girl_url)
                time.sleep(3)
                driver.find_element_by_xpath('//em[@class="ch all"]').click()
                time.sleep(3)
                html = driver.page_source
                selector = etree.HTML(html)
                self.girl_name = selector.xpath('//div[@class="article"]/h2/text()')[0]
                self.pic_urls = selector.xpath('//div[@id="content"]/img/@data-img')
                try:
                    self.download_pic()
                except Exception as e:
                    print("{}保存失败".format(self.girl_name) + str(e))
    
    # 下载图片的方法
        def download_pic(self):
            try:
                os.mkdir(PICTURES_PATH)
            except:
                pass
            girl_path = PICTURES_PATH + self.girl_name
            try:
                os.mkdir(girl_path)
            except Exception as e:
                print("{}已存在".format(self.girl_name))
            img_name = 0
            for pic_url in self.pic_urls:
                img_name += 1
                img_data = requests.get(pic_url, headers=headers)
                pic_path = girl_path + '/' + str(img_name)+'.jpg'
                if os.path.isfile(pic_path):
                    print("{}第{}张已存在".format(self.girl_name, img_name))
                    pass
                else:
                    with open(pic_path, 'wb')as f:
                        f.write(img_data.content)
                        print("正在保存{}第{}张".format(self.girl_name, img_name))
                        f.close()
            return
    
    # 爬虫的启动方法,按照爬虫逻辑依次调用方法
        def start(self):
            self.get_page_urls()
            self.get_girl_urls()
            self.get_pic_urls()
    
    
    # main函数
    if __name__ == '__main__':
        page_num = input("请输入页码:")
        mmjpg_spider = Spider(page_num)
        mmjpg_spider.start()
    

    现在你就可以慢慢爬妹子图了,注意最好在请求的时候time.sleep()几秒钟,请求太频繁的话,也有一定的概率被识别为爬虫的,虽然我并没有实验,但是我也建议你这么做,因为,过于频繁的请求还是会让服务器吃不消,看在人家的图片这么良心的情况下,爬慢点......

    仅供学习
    转载注明出处
    禁止商业用途

    相关文章

      网友评论

      • 2c8dde622255:这个网站完全没必要用selenium,但还是在reference那里受教了
        2c8dde622255:@3inchtime 415268204 这是我QQ,我写了个多线程的加一个代理ip池,可以交流下
        3inchtime:@Z1ber 对,后面打算改一下
      • Lyrus:快饶了煎蛋吧
      • 40cb4f62cae3:学习学习
      • 73bc6bd611fb:大家的import requests都不报错吗?为什么我的一直告诉说没有“requests”模块😳
        d9ce68731cdb:@草祭小子 cmd打开命令行,键入pip install requests,回车等待
        73bc6bd611fb:@草祭小子 有大佬解答下吗?度娘上各种方式都试了
      • 早安先森:UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-15: ordinal not in range(128)

        请教下这个编码报错各位大佬遇到过吗?python2.7,开头声明了utf-8
      • 6851c462f5e1:这是最基本的爬虫。能加上异步,多线程吗?
        3inchtime:@王二_17a2 打算过一段时间做一个协程的版本
      • netppp:好
      • d07713a4aacb:为什么请求带了cookie 反而得到了风景图。不带cookie却能得到正确的图片。
        3inchtime:@d07713a4aacb 这个我倒是不太清楚:anguished:
      • 就叫小木木呀:我发现一个问题,每一个新学爬虫的程序员做的第一个爬虫项目都是爬美女图:joy:,我之前也是,整整13万张图还放在硬盘里在,但是早已经没有欣赏的想法了,毕竟代码更好玩:blush:
        3inchtime:@紫陌垂杨00 对啊 对于新手简单切具有吸引力
      • 青春没有如果:为什么我拿到正确的图片链接,打开却是一张风景图。求作者指教。谢谢
        青春没有如果:@3inchtime 谢谢,我刚开始只加了User-Agent,所以不行
        3inchtime:@青春没有如果 携带headers 下载图片啊
      • RuMR:10054了....怎么解决呢...
        3inchtime:@RuMR 我自己并没有出现这个问题啊
        RuMR:@3inchtime 回复神速啊~谢谢啦~
        3inchtime:@RuMR 应该是反爬虫的原因,有空我看一下
      • 一个写Bug的前端妹子:一个前端妹子,看到你的文章,突然对python 感兴趣了。
        3inchtime:@我是周三岁 :stuck_out_tongue_closed_eyes:
      • 菜鸟小学生:由于Microsoft Visual C++ 14.0 is required无法安装lxml,求告知方法
        菜鸟小学生:@3inchtime 多谢
        3inchtime:@菜鸟小学生 https://blog.csdn.net/amoscn/article/details/78215641
      • e156012376cf:恕我直言我就是来看妹子的
        3inchtime:@九星九微 :joy:
      • 王州_52bc:我用scrapy爬了妹子图全站
      • 往事如烟散不尽:图片我存下了,作为你散布色情的证据
        早安先森:@詩言誌 哈哈啊哈哈
        3inchtime:@詩言誌 好慌
      • 大叔top:向你学习 我要学python
      • 前端小切图仔:收下我的膝盖
      • 362aeca90737:这该怎么办,显示由于连接方在一段时间后没有正确答复或连接的主机没有反应,连接尝试失败
        b1682c015c08:@情殇知秋 没有用啊,朋友! 我也学你的方式改了最后还是不行,我直接报错远程主机拒绝连接
        362aeca90737:@3inchtime 解决了,我用window跑的,改了chrome驱动的位置,然后加了后缀.exe就能用了
        3inchtime:@情殇知秋 解决了么
      • Explorer_Mi:你的节操哪。。。
      • 弘胤:大佬快带我上车,我现在学的一般。。。
      • Holy俊杰:哈哈,爬了几个 G 了?
        3inchtime:@Holy俊杰 并没有测过
      • 独秀娱乐:滴......滴滴......
      • largercode:入坑Python中,已伤痕累累((( ̄へ ̄井),看到大作,又恢复满血!:yum:
        Rokkia:呵 男人 一看到美图就满血
      • 也无风雨也无晴_928b:在get_pic_urls方法中执行到driver.get(girl_url)直接就没反应了 是什么情况。。
        Arise11:我和你的问题一样,你是怎么解决的呀??
        菩灵:666,感谢分享,激发了我学习爬虫的兴趣:blush:
        3inchtime:取消Chrome的无界面了么?可以先在有界面的Chrome下试试,如果还是没有反应可以debug调试一下,看看哪里出问题了
      • 知识学者:黄图哥,你好:grin: :grin:
        3inchtime:@东风冷雪 我只是搬运工
      • 你猜怎么滴:。。。作者我感觉你不是教学来的。。是发福利来的
        谨言:666,哥
        3inchtime:@你猜怎么滴 很基础的东西,估计网站链接是唯一有价值的东西

      本文标题:[福利向]Python妹子图爬虫

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