Python爬虫小白入门(五)PhatomJS+Selenium第二篇
一、前言
前文介绍了PhatomJS 和Selenium 的用法,工具准备完毕,我们来看看如何使用它们来改造我们之前写的小爬虫。
我们的目的是模拟页面下拉到底部,然后页面会刷出新的内容,每次会加载10张新图片。
大体思路是,用Selenium + PhatomJS 来请求网页,页面加载后模拟下拉操作,可以根据想要获取的图片多少来选择下拉的次数,然后再获取网页中的全部内容。
二、运行环境
我的运行环境如下:
系统版本
Windows10。Python版本
Python3.5,推荐使用Anaconda 这个科学计算版本,主要是因为它自带一个包管理工具,可以解决有些包安装错误的问题。去Anaconda官网,选择Python3.5版本,然后下载安装。IDE
我使用的是PyCharm,是专门为Python开发的IDE。这是JetBrians的产品,点我下载。
三、爬虫实战改造
3.1 模拟下拉操作
要想实现网页的下拉操作,需要使用Selenium的一个方法来执行js代码。该方法如下:
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
由此可见,使用execute_script方法可以调用JavaScript API在一个加载完成的页面中去执行js代码。可以做任何你想做的操作哦,只要用js写出来就可以了。
改造的爬虫的第一步就是封装一个下拉方法,这个方法要能控制下拉的次数,下拉后要有等待页面加载的时间,以及做一些信息记录(在看下面的代码前自己先想一想啦):
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就没什么问题了,程序可以顺利的执行。
但是,细心的小伙伴儿肯定发现了,我们这个爬虫在爬取的过程中中断了,或者过一段时间网站图片更新了,我再想爬,有好多图片都是重复的,却又重新爬取一遍,浪费时间。那么我们有什么办法来达到去重的效果呢?
3.2 去重
想要做去重,无非就是在爬取图片的时候记录图片名字(图片名字是唯一的)。在爬取图片前,先检查该图片是否已经爬取过,如果没有,则继续爬取,如果已经爬取过,则不进行爬取。
去重的逻辑如上,实现方式的不同主要体现在记录已经爬取过的信息的途径不同。比如可以使用数据库记录、使用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.
Python爬虫小白入门(五)PhatomJS+Selenium第二篇的更多相关文章
- Python爬虫小白入门(四)PhatomJS+Selenium第一篇
一.前言 在上一篇博文中,我们的爬虫面临着一个问题,在爬取Unsplash网站的时候,由于网站是下拉刷新,并没有分页.所以不能够通过页码获取页面的url来分别发送网络请求.我也尝试了其他方式,比如下拉 ...
- Python爬虫教程——入门五之URLError异常处理
大家好,本节在这里主要说的是URLError还有HTTPError,以及对它们的一些处理. 1.URLError 首先解释下URLError可能产生的原因: 网络无连接,即本机无法上网 连接不到特定的 ...
- Python爬虫小白入门(六)爬取披头士乐队历年专辑封面-网易云音乐
一.前言 前文说过我的设计师小伙伴的设计需求,他想做一个披头士乐队历年专辑的瀑布图. 通过搜索,发现网易云音乐上有比较全的历年专辑信息加配图,图片质量还可以,虽然有大有小. 我的例子怎么都是爬取图片? ...
- Python爬虫小白入门(一)写在前面
一.前言 你是不是在为想收集数据而不知道如何收集而着急? 你是不是在为想学习爬虫而找不到一个专门为小白写的教程而烦恼? Bingo! 你没有看错,这就是专门面向小白学习爬虫而写的!我会采用实例的方式, ...
- [Python爬虫] 之十五:Selenium +phantomjs根据微信公众号抓取微信文章
借助搜索微信搜索引擎进行抓取 抓取过程 1.首先在搜狗的微信搜索页面测试一下,这样能够让我们的思路更加清晰 在搜索引擎上使用微信公众号英文名进行“搜公众号”操作(因为公众号英文名是公众号唯一的,而中文 ...
- Python爬虫小白入门(一)入门介绍
一.前言 你是不是在为想收集数据而不知道如何收集而着急? 你是不是在为想学习爬虫而找不到一个专门为小白写的教程而烦恼? Bingo! 你没有看错,这就是专门面向小白学习爬虫而写的!我会采用实例的方式, ...
- Python爬虫小白入门(三)BeautifulSoup库
# 一.前言 *** 上一篇演示了如何使用requests模块向网站发送http请求,获取到网页的HTML数据.这篇来演示如何使用BeautifulSoup模块来从HTML文本中提取我们想要的数据. ...
- Python爬虫小白入门(二)requests库
一.前言 为什么要先说Requests库呢,因为这是个功能很强大的网络请求库,可以实现跟浏览器一样发送各种HTTP请求来获取网站的数据.网络上的模块.库.包指的都是同一种东西,所以后文中可能会在不同地 ...
- Python爬虫小白入门(七)爬取豆瓣音乐top250
抓取目标: 豆瓣音乐top250的歌名.作者(专辑).评分和歌曲链接 使用工具: requests + lxml + xpath. 我认为这种工具组合是最适合初学者的,requests比pytho ...
随机推荐
- 在项目管理工具Redmine中使用SubVersion进行版本管理
原文:在项目管理工具Redmine中使用SubVersion进行版本管理 在项目管理工具Redmine中使用SubVersion进行版本管理 分类: Redmine2009-06-01 10:11 5 ...
- NYOJ 58 步数最少 【BFS】
意甲冠军:不解释. 策略:如果: 这个问题也可以用深宽搜索搜索中使用.我曾经写过,使用深层搜索.最近的学校范围内的搜索,拿这个问题来试试你的手. 代码: #include<stdio.h> ...
- 将Model实体类对象作为WebService接口参数(转)
转自:http://www.cnblogs.com/fengyishou/archive/2009/02/27/1399281.html 关于web服务的有关基础知识,看了基本书,但是不敢在这里乱说, ...
- WCF 服务端异常封装
通常WCF服务端异常的详细信息只有在调试环境下才暴露出来,但我目前有需求需要将一部分异常的详细信息传递到客户端,又需要保证一定的安全性. 最简单的办法当然是在服务端将异常捕获后,序列化传给客户端,但这 ...
- 如何在局域网安装Redmine(转贴)
如何在局域网安装Redmine(转贴) 分类: Redmine2009-06-01 10:31 1740人阅读 评论(0) 收藏 举报 phpmyadmin项目管理railssubversion数据库 ...
- Javascript Array API
JS数组对象提供了很多API方法,由于前段时间要用到某一些方法,但是突然一时又想不起来该怎么用了,上网找有很多资料都不全,所以就自己整理了一篇,完全是自己写的的JS,只是复制到这里来了 ,要用到的朋友 ...
- ASP.NET MVC Model绑定
ASP.NET MVC Model绑定(一) 前言 ModelMetadata系列的结束了,从本篇开始就进入Model绑定部分了,这个系列阅读过后你会对Model绑定有个比较清楚的了解, 本篇对于Mo ...
- Node填坑教程——简易http服务器
我们这一期做一个简易的http服务器. 先建一个文件夹,就算是一个空的项目了.然后新建app.js和package.json文件. 这样一个简易项目的基本文件就建好了. 通过命令行工具,在项目路径下输 ...
- 表达式树解析"框架"
干货!表达式树解析"框架"(2) 为了过个好年,我还是赶快把这篇完成了吧 声明 本文内容需要有一定基础的开发人员才可轻松阅读,如果有难以理解的地方可以跟帖询问,但我也不一定能回 ...
- Hadoop作业提交之TaskTracker获取Task
[Hadoop代码笔记]Hadoop作业提交之TaskTracker获取Task 一.概要描述 在上上一篇博文和上一篇博文中分别描述了jobTracker和其服务(功能)模块初始化完成后,接收JobC ...