Selenium + BeautifulSoup爬虫入门,对于JS加载的网页数据无法读取的解决办法
  • 分类:Python
  • 发表:2019-05-08
  • 围观(7,898)
  • 评论(1)


问题来源

爬虫写完并不是一劳永逸的,因为原站的代码迭代,我们上次文章写的代码就变成了a pile of shit💩。生活不如意,十有八九,这并不是阻碍我们学习的绊脚石。我们出发点是学习,为了使这次对的代码具有一定的Robust(健壮性),同样为了学习新的技术。这次采用Selenium的方式来编写爬虫的代码,或许情况会有所好转/(ㄒoㄒ)/~~。

什么时候用Requests?

通常我们在对网页进行抓包的时候,我们通常期待能找到网站的API请求接口,API可以通过参数修改进行构造。返回结果最好是标准的Json格式,这样也方便我们在Python内做数据的持久化。像这种情况我们用requests模块无疑是最好的选择,因为这样的代码速度快质量高。但现实确实很残酷的,多数网站都会通过JS动态填充网页数据来防止爬虫。采取这种措施的网页请求返回的只有网页的框架,具体的数据都是通过Ajax动态渲染到网页内的。requests是没有加载执行JS的能力的,所以我们没办法通过requests的返回得到真实数据。

什么时候用Selenium?

对于这种JS填充数据的网站,解决的方法有各种各样。通常的方法还是通过抓包获取到JS获取数据的接口,优势之前说过了,这样写的代码比较高效。缺点也很明显,你需要有一定的JS调试基础。因为有些接口你虽然找到了,但是接口传的的参数经过JS加密,需要对JS进行分析找到加密算法,否则即使抓到了API也无法使用。对于简单的JS或许可以在浏览器内调试一下,但是如果是经过混淆压缩的JS代码简直让人头都大了,这对于我们新手难度有点大。通过Selenium就可以化繁为简,轻松的实现我们需要的功能。


什么是Selenium?

什么是Selenium?-引自维基百科

Selenium就是一个web自动化测试框架。他允许用户通过编写代码操纵浏览器,实现像真正用户一样的浏览器操作行为。对于一些反爬机制比较苛刻的网站,我们可以用Selenium操作浏览器模拟真实用户操作来爬取网站数据。这样的好处就是无需考虑请求的发送,cookie,user-agent等等一系列的要求,只要编写代码操作浏览器,通过读取浏览器内容获取想要的数据。浏览器具有执行JS的能力,在网页加载完成之后读取网页源码,就可以取到我们想要的数据。

接下来我们通过Selenium改写我们之前的爬虫代码,代码稍简单,仅供入门参考。


环境搭建

在Python中使用Selenium需要安装相应的环境。安装环境十分简单大概分为三步:

  1. 在虚拟环境的控制台使用命令:pip install selenium安装相应的包支持库
  2. 添加对应浏览器的路径到系统环境变量。例如我这边用的是Chrome浏览器,就需要将Chrome根目录文件夹添加到Path环境变量里。这个操作对一个开发者来说并不会陌生
  3. 根据浏览器版本下载对应的驱动。驱动下载地址(点击跳转),注意一定要对应自己浏览器的版本。下载完成之后将文件解压到Python的安装目录即可

安装完成后运行以下代码测试是否安装成功,安装成功会运行Chrome打开本站,并在控制台输出网页源代码:

from selenium import webdriver

browser = webdriver.Chrome()
browser.get("https://www.weiney.com/")
print(brower.page_sourse)

爬虫分析

抓取网站:http://yoerking.com/static/music_player.html
运行环境:Python 3.6.5
支持库:selenium,requests,beautifulsoup,tqdm

对比之前的代码,最主要的不同就在于之前的网页代码是用requests模块完成的。这次替换成了用selenium从浏览器内获取网页源代码。这样就节省了分析js的麻烦。但是每次运行爬虫的时候都会打开Chrome同样会使得软件效率降低,但是影响并不大,没有requests快就是了。

def get_page_sourse():
    browser = webdriver.Chrome()
    browser.get(URL)
    page_sourse = browser.page_source
    browser.close()
    return page_sourse
def parse_music():
    page_sourse = get_page_sourse()
    soup = BeautifulSoup(page_sourse, "html.parser")
    all_musics = soup.find_all("a", class_="url")
    for music_item in all_musics:
        single_music = dict()
        single_music["url"] = music_item.attrs["hrefsrc"]
        single_music["name"] = music_item.text
        single_music["album"] = re.search("(?<=music/)(.*?)(?=/)", single_music["url"]).group()
        ALL_MUSICS.append(single_music)
class DownloadThread(threading.Thread):
    def __init__(self, single_music, phar):
        threading.Thread.__init__(self)
        self.single_music = single_music
        self.phar = phar

    def run(self) -> None:
        req = requests.get(self.single_music["url"])
        with open(PATH + self.single_music["album"] + "\\" + self.single_music["name"], "wb") as f:
            f.write(req.content)
            self.phar.update(1)

数据解析使用BeautifulSoup处理的。解析出专辑、歌曲名、资源url之后创建下载线程,把数据交给下载类处理。线程数定义合理的话带宽跑满速完全没有问题。顺便用tqdm模块给脚本加了一个可视化进度条,不然没有交互的程序让人头大。或许我不应该下载的,站主说服务器带宽是按G收费的,仅仅出于学习测试目的,不做坏事。脚本运行的截图如下:

脚本运行截图

下载专辑截图

总结

Selenium不但可以做网页自动化测试,还能为网页爬虫提供支持。对我来说确实是应该深入研究的的框架。希望自己能在后面的时间里更加系统的看一看Selenium的文档,我相信这对以后的学习派的上用场。

爬虫的实现方法多种多样,或许大家都会选择最简单的。但是考虑到网站的迭代更新,为了使爬虫更加健壮,我们可以选择效率低但是稳定的写法,就像本文中展示一样。但是对于大规模的爬虫,显然这种低效率是无法忍受的,为了效率忍受复杂是可以接受的。根据实际情况选择最时候的方法显得尤为重要。

import time
from bs4 import BeautifulSoup
from selenium import webdriver
import re
import threading
import requests
import os
from tqdm import tqdm

if not os.path.exists("album"):
    os.mkdir("album")

PATH = os.getcwd() + "\\album\\"

URL = "http://yoerking.com/static/music_player.html"

ALL_MUSICS = []


def get_page_sourse():
    browser = webdriver.Chrome()
    browser.get(URL)
    page_sourse = browser.page_source
    browser.close()
    return page_sourse


def parse_music():
    page_sourse = get_page_sourse()
    soup = BeautifulSoup(page_sourse, "html.parser")
    all_musics = soup.find_all("a", class_="url")
    for music_item in all_musics:
        single_music = dict()
        single_music["url"] = music_item.attrs["hrefsrc"]
        single_music["name"] = music_item.text
        single_music["album"] = re.search("(?<=music/)(.*?)(?=/)", single_music["url"]).group()
        ALL_MUSICS.append(single_music)


def create_dir(albums):
    for album in albums:
        if not os.path.exists(PATH + album):
            os.mkdir(PATH + album)


class DownloadThread(threading.Thread):
    def __init__(self, single_music, phar):
        threading.Thread.__init__(self)
        self.single_music = single_music
        self.phar = phar

    def run(self) -> None:
        req = requests.get(self.single_music["url"])
        with open(PATH + self.single_music["album"] + "\\" + self.single_music["name"], "wb") as f:
            f.write(req.content)
            self.phar.update(1)


if __name__ == '__main__':
    parse_music()
    ALL_ALBUMS = set([album["album"] for album in ALL_MUSICS])
    print("获取到专辑数量:{}, 歌曲数量:{}".format(len(ALL_ALBUMS), len(ALL_MUSICS)))
    print("正在创建专辑目录")
    create_dir(ALL_ALBUMS)
    print("专辑目录创建完成,开始下载歌曲")
    with tqdm(total=len(ALL_MUSICS)) as phar:
        for single in ALL_MUSICS:
            while threading.active_count() > 20:
                time.sleep(1)
            thread = DownloadThread(single, phar)
            thread.start()

    input("爬虫执行完成,按任意键退出")

共有 1 条评论

  1. 赵凯

    写得很好

Top