前文介绍了PhatomJS 和Selenium 的用法,工具准备完毕,咱们来看看如何使用它们来改造咱们以前写的小爬虫。git
咱们的目的是模拟页面下拉到底部,而后页面会刷出新的内容,每次会加载10张新图片。github
大致思路是,用Selenium + PhatomJS 来请求网页,页面加载后模拟下拉操做,能够根据想要获取的图片多少来选择下拉的次数,而后再获取网页中的所有内容。web
个人运行环境以下:chrome
系统版本
Windows10。数据库
Python版本
Python3.5,推荐使用Anaconda 这个科学计算版本,主要是由于它自带一个包管理工具,能够解决有些包安装错误的问题。去Anaconda官网,选择Python3.5版本,而后下载安装。浏览器
IDE
我使用的是PyCharm,是专门为Python开发的IDE。这是JetBrians的产品,点我下载。网络
要想实现网页的下拉操做,须要使用Selenium的一个方法来执行js代码。该方法以下:
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
框架
因而可知,使用execute_script
方法能够调用JavaScript API在一个加载完成的页面中去执行js代码。能够作任何你想作的操做哦,只要用js写出来就能够了。ide
改造的爬虫的第一步就是封装一个下拉方法,这个方法要能控制下拉的次数,下拉后要有等待页面加载的时间,以及作一些信息记录(在看下面的代码前本身先想想啦):函数
def scroll_down(self, driver, times): for i in range(times): print("开始执行第", str(i + 1),"次下拉操做") driver.execute_script("window.scrollTo(0, document.body.scrollHeight);") #执行JavaScript实现网页下拉倒底部 print("第", str(i + 1), "次下拉操做执行完毕") print("第", str(i + 1), "次等待网页加载......") time.sleep(20) # 等待20秒(时间能够根据本身的网速而定),页面加载出来再执行下拉操做
这部分作完以后就是修改页面爬取逻辑,以前是使用request 请求网址,而后找到图片url所在位置,再而后挨个请求图片url。如今,咱们要首先使用Selenium 请求网址,而后模拟下拉操做,等待页面加载完毕再遵循原有逻辑挨个请求图片的url。
逻辑部分改造以下:
def get_pic(self): print('开始网页get请求') # 使用selenium经过PhantomJS来进行网络请求 driver = webdriver.PhantomJS() driver.get(self.web_url) self.scroll_down(driver=driver, times=5) #执行网页下拉到底部操做,执行5次 print('开始获取全部a标签') all_a = BeautifulSoup(driver.page_source, 'lxml').find_all('a', class_='cV68d') #获取网页中的class为cV68d的全部a标签 print('开始建立文件夹') self.mkdir(self.folder_path) #建立文件夹 print('开始切换文件夹') os.chdir(self.folder_path) #切换路径至上面建立的文件夹 print("a标签的数量是:", len(all_a)) #这里添加一个查询图片标签的数量,来检查咱们下拉操做是否有误 for a in all_a: #循环每一个标签,获取标签中图片的url而且进行网络请求,最后保存图片 img_str = a['style'] #a标签中完整的style字符串 print('a标签的style内容是:', img_str) first_pos = img_str.index('"') + 1 #获取第一个双引号的位置,而后加1就是url的起始位置 second_pos = img_str.index('"', first_pos) #获取第二个双引号的位置 img_url = img_str[first_pos: second_pos] #使用Python的切片功能截取双引号之间的内容 #注:为了尽快看到下拉加载的效果,截取高度和宽度部分暂时注释掉,由于图片较大,请求时间较长。 ##获取高度和宽度的字符在字符串中的位置 #width_pos = img_url.index('&w=') #height_pos = img_url.index('&q=') #width_height_str = img_url[width_pos : height_pos] #使用切片功能截取高度和宽度参数,后面用来将该参数替换掉 #print('高度和宽度数据字符串是:', width_height_str) #img_url_final = img_url.replace(width_height_str, '') #把高度和宽度的字符串替换成空字符 #print('截取后的图片的url是:', img_url_final) #截取url中参数前面、网址后面的字符串为图片名 name_start_pos = img_url.index('photo') name_end_pos = img_url.index('?') img_name = img_url[name_start_pos : name_end_pos] self.save_img(img_url, img_name) #调用save_img方法来保存图片
逻辑修改完毕。执行一下,发现报错了,图片的url截取错误。那就看看究竟是为何,先输出找到url看看:
看看输出内容,发现经过PhatomJS 请求网页得到的url(https://blablabla) 中,居然没有双引号,这跟使用Chrome 看到的不同。好吧,那就按照没有双引号的字符串从新截取图片的url。
其实,咱们只须要把url的起始位置和结束逻辑改为经过小括号来获取就能够了:
first_pos = img_str.index('(') + 1 #起始位置是小括号的左边 second_pos = img_str.index(')') #结束位置是小括号的右边
好啦,此次获取图片的url就没什么问题了,程序能够顺利的执行。
可是,细心的小伙伴儿确定发现了,咱们这个爬虫在爬取的过程当中中断了,或者过一段时间网站图片更新了,我再想爬,有好多图片都是重复的,却又从新爬取一遍,浪费时间。那么咱们有什么办法来达到去重的效果呢?
想要作去重,无非就是在爬取图片的时候记录图片名字(图片名字是惟一的)。在爬取图片前,先检查该图片是否已经爬取过,若是没有,则继续爬取,若是已经爬取过,则不进行爬取。
去重的逻辑如上,实现方式的不一样主要体如今记录已经爬取过的信息的途径不一样。好比可使用数据库记录、使用log文件记录,或者直接检查已经爬取过的数据信息。
根据爬取内容的不一样实现方式也不一样。咱们这里先使用去文件夹下获取全部文件名,而后在爬取的时候作对比。
后面的爬虫实战再使用其余方式来作去重。
单从爬取unsplash 网站图片这个实例来看,须要知道文件夹中全部文件的名字就够了。
Python os 模块中有两种可以获取文件夹下全部文件名的方法,分别来个示例:
for root, dirs, files in os.walk(path): for file in files: print(file)
for file in os.listdir(path): print(file)
其中,os.walk(path) 是获取path文件夹下全部文件名和全部其子目录中的文件夹名和文件名。
而os.listdir(path) 只是获取path文件夹下的全部文件的名字,并不care其子文件夹。
这里咱们使用os.listdir(path) 就能知足需求。
写一个获取文件夹内全部文件名的方法:
def get_files(self, path): pic_names = os.listdir(path) return pic_names
由于在保存图片以前就要用到文件名的对比,因此把save_img方法修改了一下,在方法外部拼接图片名字,而后直接做为参数传入,而不是在图片存储的过程当中命名:
def save_img(self, url, file_name): ##保存图片 print('开始请求图片地址,过程会有点长...') img = self.request(url) print('开始保存图片') f = open(file_name, 'ab') f.write(img.content) print(file_name,'图片保存成功!') f.close()
为了更清晰去重逻辑,咱们每次启动爬虫的时候检测一下图片存放的文件夹是否存在,若是存在则检测与文件夹中的文件作对比,若是不存在则不须要作对比(由于是新建的嘛,文件夹里面确定什么文件都没有)。
逻辑修改以下,是新建的则返回True,不是新建的则返回False:
def mkdir(self, path): ##这个函数建立文件夹 path = path.strip() isExists = os.path.exists(path) if not isExists: print('建立名字叫作', path, '的文件夹') os.makedirs(path) print('建立成功!') return True else: print(path, '文件夹已经存在了,再也不建立') return False
而后修改咱们的爬取逻辑部分:
主要是添加获取的文件夹中的文件名列表,而后判断图片是否存在。
is_new_folder = self.mkdir(self.folder_path) #建立文件夹,并判断是不是新建立
file_names = self.get_files(self.folder_path) #获取文件家中的全部文件名,类型是list
if is_new_folder: self.save_img(img_url, img_name) # 调用save_img方法来保存图片 else: if img_name not in file_names: self.save_img(img_url, img_name) # 调用save_img方法来保存图片 else: print("该图片已经存在:", img_name, ",再也不从新下载。")
好了,来个完整版代码(也能够去 GitHub下载):
from selenium import webdriver #导入Selenium import requests from bs4 import BeautifulSoup #导入BeautifulSoup 模块 import os #导入os模块 import time class BeautifulPicture(): def __init__(self): #类的初始化操做 self.headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1'} #给请求指定一个请求头来模拟chrome浏览器 self.web_url = 'https://unsplash.com' #要访问的网页地址 self.folder_path = 'C:\D\BeautifulPicture' #设置图片要存放的文件目录 def get_pic(self): print('开始网页get请求') # 使用selenium经过PhantomJS来进行网络请求 driver = webdriver.PhantomJS() driver.get(self.web_url) self.scroll_down(driver=driver, times=3) #执行网页下拉到底部操做,执行3次 print('开始获取全部a标签') all_a = BeautifulSoup(driver.page_source, 'lxml').find_all('a', class_='cV68d') #获取网页中的class为cV68d的全部a标签 print('开始建立文件夹') is_new_folder = self.mkdir(self.folder_path) #建立文件夹,并判断是不是新建立 print('开始切换文件夹') os.chdir(self.folder_path) #切换路径至上面建立的文件夹 print("a标签的数量是:", len(all_a)) #这里添加一个查询图片标签的数量,来检查咱们下拉操做是否有误 file_names = self.get_files(self.folder_path) #获取文件家中的全部文件名,类型是list for a in all_a: #循环每一个标签,获取标签中图片的url而且进行网络请求,最后保存图片 img_str = a['style'] #a标签中完整的style字符串 print('a标签的style内容是:', img_str) first_pos = img_str.index('(') + 1 second_pos = img_str.index(')') img_url = img_str[first_pos: second_pos] #使用Python的切片功能截取双引号之间的内容 # 注:为了尽快看到下拉加载的效果,截取高度和宽度部分暂时注释掉,由于图片较大,请求时间较长。 #获取高度和宽度的字符在字符串中的位置 # width_pos = img_url.index('&w=') # height_pos = img_url.index('&q=') # width_height_str = img_url[width_pos : height_pos] #使用切片功能截取高度和宽度参数,后面用来将该参数替换掉 # print('高度和宽度数据字符串是:', width_height_str) # img_url_final = img_url.replace(width_height_str, '') #把高度和宽度的字符串替换成空字符 # print('截取后的图片的url是:', img_url_final) #截取url中参数前面、网址后面的字符串为图片名 name_start_pos = img_url.index('.com/') + 5 #经过找.com/的位置,来肯定它以后的字符位置 name_end_pos = img_url.index('?') img_name = img_url[name_start_pos : name_end_pos] + '.jpg' img_name = img_name.replace('/','') #把图片名字中的斜杠都去掉 if is_new_folder: self.save_img(img_url, img_name) # 调用save_img方法来保存图片 else: if img_name not in file_names: self.save_img(img_url, img_name) # 调用save_img方法来保存图片 else: print("该图片已经存在:", img_name, ",再也不从新下载。") def save_img(self, url, file_name): ##保存图片 print('开始请求图片地址,过程会有点长...') img = self.request(url) print('开始保存图片') f = open(file_name, 'ab') f.write(img.content) print(file_name,'图片保存成功!') f.close() def request(self, url): #返回网页的response r = requests.get(url) # 像目标url地址发送get请求,返回一个response对象。有没有headers参数均可以。 return r def mkdir(self, path): ##这个函数建立文件夹 path = path.strip() isExists = os.path.exists(path) if not isExists: print('建立名字叫作', path, '的文件夹') os.makedirs(path) print('建立成功!') return True else: print(path, '文件夹已经存在了,再也不建立') return False def scroll_down(self, driver, times): for i in range(times): print("开始执行第", str(i + 1),"次下拉操做") driver.execute_script("window.scrollTo(0, document.body.scrollHeight);") #执行JavaScript实现网页下拉倒底部 print("第", str(i + 1), "次下拉操做执行完毕") print("第", str(i + 1), "次等待网页加载......") time.sleep(30) # 等待30秒,页面加载出来再执行下拉操做 def get_files(self, path): pic_names = os.listdir(path) return pic_names beauty = BeautifulPicture() #建立类的实例 beauty.get_pic() #执行类中的方法
注释写的很详细,有任何问题能够留言。
该实战就先到这里,须要注意的是Unsplash 这个网站常常不稳定,小伙伴有遇到请求异常的状况能够多试几回。
后面我开始写一个查找The Beatles 乐队的历年专辑封面图片和专辑名称的爬虫。个人设计师小伙伴儿想要收集Beatles 乐队历年的专辑封面图片,而后作一个该乐队的设计海报。
至于爬虫框架的使用,后面我会再专门介绍,最好找到一个好的使用框架的实战例子,这样才能更好的理解框架的做用与优势。
OK, See you then.