项目地址 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个妹子,那么......
所以,我采取的策略是这样的:
首先应该输入我们需要爬取到多少页。(例如20页,那么我们就爬取1-20页。)
我们依次爬取这些页面上所有妹子的图片页面。(例如第1页有15个妹子,那么我们就把这15个妹子的url爬下来。)
最后我们进入妹子的页面,依次爬取下妹子的图片,并保存在本地。
当然了,这肯定不是最优的爬取策略,如果你有更好的策略,请在评论区留言。
我的开发环境为 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 etreeclass 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): passif __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拼接来获取图片的链接了,那我们现在该怎么办呢?
我个人有两种解决办法:
一页一页的获取图片的url,我们可以获取第一页上的图片之后,进入下一页爬取下一页的图片,以此类推,直到爬下这个妹子所有的图片。
这个办法是一种很常规的做法,很容易实现,但是不够优雅。我们发现每一页都有一个按钮 “全部图片” ,我们点击全部图片之后,我们就看到了所有图片的url。
这样我们就得到了所有图片的url,那么问题来了,爬虫本身并不具备点击这个按钮的功能,但是要想获取所有图片的url,我们必须点击这个按钮之后才能得到,怎么办呢?
这时,我就要向你们介绍一大神器——Selenium
Selenium 是为了测试而出生的. 但是没想到到了爬虫的年代, 它摇身一变, 变成了爬虫的好工具。
Seleninm: 它能控制你的浏览器, 有模有样地学人类”看”网页。
Selenium可以操纵浏览器浏览网页,而且它可以点击按钮,甚至输入文字,是不是很神奇?
Selenium可以操纵的浏览器有很多,我选择了比较常用的Chrome。
pip install selenium
之后下载你需要操纵的浏览器对应的驱动。
https://www.seleniumhq.org/download/
下载你需要的驱动,到你常用的目录就可以啦!
下面我们使用Selenium来模拟点击“全部图片”的按钮,来获取所有图片的url了。
import timefrom 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 osimport requestsfrom lxml import etreeimport timefrom 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/')# 设置headersheaders = { '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()
几秒钟,请求太频繁的话,也有一定的概率被识别为爬虫的,虽然我并没有实验,但是我也建议你这么做,因为,过于频繁的请求还是会让服务器吃不消,看在人家的图片这么良心的情况下,爬慢点......
作者:3inchtime
链接:https://www.jianshu.com/p/4a54ccdb73b7