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.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,421评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,594评论 18 139
  • 励志30年成就3000万中国人魅力表达,身心富足。每日必推送一篇有关演讲与口才的文章,你我共同挑战韧性吧,我挑战写...
    铂津阅读 252评论 0 0
  • DM大猫阅读 210评论 0 2
  • 细雨落,润你我, 曾几何时, 你我,同堂坐。 日落共回味, 梦呓欣喜把笑说。 乐,乐,乐! 世事多,难捉摸, 昔时...
    火猴阅读 482评论 4 1