LongLong's Blog

分享IT技术,分享生活感悟,热爱摄影,热爱航天。

使用Selenium实现浏览器爬取网页

尽管早已经进入移动App的世代,但是大部分服务商还是会提供便于PC使用的网页版,而在Web时代最长做的一个事情就是抓取网络上的数据,并将抓取的数据进行整理分析,其实如今的不少类似的需求应该还是延续这种方式,毕竟通用性强,不需要针对不同类型的App逐个反向研究App内部的通信协议。

而在之前通常使用的方法是使用基于各中编程语言提供的HTTP客户端API进行直接的数据抓取,这种方法直接获取有用的数据,有着效率高同时节约网络带宽的特点。但如今即使是Web端也同样利用的大量的Javascrip技术,网页中大量数据都是在网页HTML框架加载完成后才进行,要想抓取其中有用的数据还需要搞清楚整个网页的运行机理,同时各种Javascript代码还进行了各种精简化很难读懂,为此通脚本直接过操控浏览器的方式就成为了一种实现更加简单,并且由于是完全模拟浏览器的行为,在服务端进行识别和封禁也相对会比较难,有着更好的适应性,缺点也同样明显——效率低占用资源也相对较高。

1. Selenium简介

Selenium 通过使用WebDriver支持市场上所有主流浏览器的自动化。 Webdriver是一个API和协议,它定义了一个语言中立的接口,用于控制Web浏览器的行为。每个浏览器都有一个特定的WebDriver实现,称为驱动程序。驱动程序是负责委派给浏览器的组件,并处理与 Selenium 和浏览器之间的通信。其本质就是一种浏览器的操作接口,可以实现对各种浏览器的自动化操作。

这里将以Python语言和Firefox浏览器为例介绍Selenium的使用方法,使用前需要下载Firefox浏览器对应的驱动程序Geckodriver,在Windows下可将下载好的geckodriver.exe程序放入Firefox的安装目录下。

2. 初始化

在使用前需要通过对应的驱动程序初始化对应的浏览器实例,其中比较关键的是设置浏览器的工作目录,如果不进行手动设置Firefox会每次自己都建立一个新的临时目录,会导致每次运行是一些持久化数据都消失掉

from selenium import webdriver;
from selenium.webdriver.firefox.service import Service;

#增加环境变量,指向驱动程序所在目录
os.putenv(NAME, os.getenv(NAME) + ';C:\Program Files\Mozilla Firefox');
#设置浏览器配置
options = webdriver.FirefoxOptions();
#不显示浏览器的图形界面
options.add_argument('--headless');
#设置浏览器的工作目录
options.add_argument('-profile');
options.add_argument('E:\TMP\FF');
#初始化浏览器实例
browser = webdriver.Firefox(options=options, service=Service(log_path=os.devnull));

3. 访问页面和查找元素

通过get方法可以访问对应URL的页面

browser.get('https://www.baidu.com');

通过find_element或是find_elements方法可以找到对应的元素,其中这两个方法的不同在于find_element只会找其中第一个匹配的元素,同时在找不到时抛出异常,而find_elements则会返回全部的结果列表,同时在找不到时返回空列表,可根据实际的需要使用对应的方法。查找的方式可以通过css选择其或是通过连接的内容等进行,与jQuery等浏览器中的操作方式基本相同。

from selenium.webdriver.common.by import By;
e = browser.find_element(By.CSS_SELECTOR, 'div .sku-name');
e = browser.find_element(By.CSS_SELECTOR, '#store-prompt > strong');
e = browser.find_element(By.LINK_TEXT, '登录');

4. 元素的操作

对于获得的元素可以进行类似浏览器前端中的各种操作——触发点击、输入等动作或是获取元素的属性及内部文本信息等。

e.send_keys('username');
e.click();
print(e.text);

5. 完整的例子

以下是一个完整的例子,可以用于获取列表中京东的商品价格,出于省事用于每天看看自己想买的东西有没有降价^_^

from selenium import webdriver;
from selenium.webdriver.firefox.service import Service;
from selenium.webdriver.common.by import By;
import os;
import time;

if __name__ == '__main__':
    NAME = 'PATH';
    os.putenv(NAME, os.getenv(NAME) + ';C:\Program Files\Mozilla Firefox');
    options = webdriver.FirefoxOptions();
    options.add_argument('--headless');
    options.add_argument('-profile');
    options.add_argument('E:\TMP\FF');
    browser = webdriver.Firefox(options=options, service=Service(log_path=os.devnull));
    #商品连接列表
    URLS = [''];
    for url in URLS:
        browser.get(url);
        time.sleep(0.5);
        #获取商品名称
        e = browser.find_element(By.CSS_SELECTOR, 'div .sku-name');
        name = e.text;
        #获取商品状态,即是否有货
        e = browser.find_element(By.CSS_SELECTOR, '#store-prompt > strong');
        status = e.text;
        #获取价格
        elements = browser.find_elements(By.CSS_SELECTOR, 'span .price');
        e1 = elements[0];
        e2 = elements[1];
        #如果有第二个价格说明是预购状态,进一步确定尾款金额
        if e2.text == '':
            price = e1.text;
        else:
            price = e2.text;
            elements = browser.find_elements(By.CSS_SELECTOR, '.yy-category');
            if elements != '':
                e3 = elements[0];
                #如果定金可额外抵扣一部分尾款,则计算实际的商品价格
                if e3.text != '':
                    #获取定价额度
                    e4 = browser.find_element(By.CSS_SELECTOR, 'span .J-earnest');
                    #产品实际价格为:尾款金额-抵扣金额+定金
                    price = float(e2.text) - float(e3.text.replace('可抵¥', '')) + float(e4.text);
        #输出得到的价格
        print(price, "\t", status, "\t", name);
        time.sleep(1.0);
    time.sleep(2.0);
    #退出
    browser.quit();

基于稀疏网格的窄带拓扑优化

拓扑优化作为结构设计中优化设计的一种自动化的手段,结合增材制造技术可以实现结构的超轻量化。但由于拓扑优化的过程中需要反复进行有限元的求解,计算量极大较难实现高分辨率模型的优化。

taichi语言的作者胡渊鸣在文章Narrow-Band Topology Optimization on a Sparsely Populated Grid中给出了一种基于稀疏多重网格预条件共轭梯度法的计算方法,其可以大幅节约计算资源实现在单台PC上进行高分辨率的拓扑优化,并给出了程序的源代码,这里主要对该代码的配置和使用做简要介绍。

1. 拓扑优化

拓扑优化(topology optimization)是一种根据给定的负载情况、约束条件和性能指标,在给定的区域内对材料分布进行优化的数学方法,是结构优化的一种。其本质上就是求一个材料的分布函数,使得在给定的边界条件下结构达到某种最优的状态,一般情况下会让结构的变形能最小(一定意义上的刚度最大化),优化的过程也是基于梯度下降的方式——沿着梯度方向更新待求函数,同时为了避免出现棋盘效应,还需要对相邻的位置进行平均化。

由于优化的目标函数一般都是节点位移的函数,为此每一步优化都需要进行一次完成的有限元求解过程——计算单元刚度矩阵,集成总体刚度矩阵,求解方程组,对于规模较大的问题,进行一次求解就需要花费大量的时间,导致拓扑优化的网格分辨率无法提升到很高,从而无法对结构的微观结构进行进一步优化。

随着稀疏存储技术和多重网格预条件方法的引入,使得计算更大规模的拓扑优化问题成为可能,以此优化后的结构在细观上会呈现出一定的仿生特征。

2. 运行环境

运行环境的核心是编译遗产(legacy)版本的taichi和代码中附带的一个SPGrid求解器,其用到了Intel编译器和MKL的一些特性,为此还需要安装Intel Parallel Studio Xe。

代码说明中中推荐的系统版本为Ubuntu18.04,经测试确实是可以的,而且20.04之后的版本都是不行的,同时其建议的Intel Parallel Studio Xe为2018版,但实际中如果使用2018版会发现其不能识别Ubuntu18.04,编译会因无法识别系统而报错,虽然可以通过一些方法解决,但如果使用2019版本的Intel Parallel Studio Xe则不会存在这个问题。

目前Intel的官网已经找不到Intel Parallel Studio Xe的下载地址,取而代之的是Intel oneAPI,经测试二者并不能兼容,不过幸好我还是找到了Intel Parallel Studio Xe的下载地址,而且该地址目前依然可用,但其为商业版需要Intel提供的序列号或是证书文件。 Intel Parallel Studio Xe 2019

3. 安装taichi遗产版

可通过提供的Python脚本之间自动化安装,需要提前安装好python3和git,但实际运行中还会缺少很多依赖库,可在报错后安装即可。

wget https://raw.githubusercontent.com/yuanming-hu/taichi/legacy/install.py
python3 install.py

4. 编译SPGrid Solver

首先将spgrid_topo_opt的代码放置到taichi源代码目录下的projects目录下,配置好Intel编译器的配置后,修改文件SPGrid_SIMD_Utilities.h,在其include部分增加

#include <immintrin.h>

同时还可以去掉Makefile中的-DHASWELL,避免编译中产生大量重复定义的报错,然后就可以开始编译SPGrid Solver,如果CPU支持AVX512(目前能够完整支持的普通桌面CPU应该只有Intel的11代Core)则应该可以使用Makefile中被注释掉的一行进行编译

source /opt/intel/bin/compilervars.sh -arch intel64
cd solver
make

5. 编译其余部分

其余部分将作为taichi的一部分,退回到spgrid_topo_opt目录下使用ti build指令进行编译,编译前需要定义三个环境变量,分别为MLK路径,GPU对应的CUDA架构版本(没有可以设为0,具体值可以在官网查表),使用double类型(这个必须设置为1否则后面编译会报错),同时如果需要使用对计算结果的可视化功能必须具有CUDA支持才可以

export TC_MKL_PATH=/opt/intel/compilers_and_libraries_2019/linux/mkl/lib/intel64_lin/
#Pascal架构为6.1,Turing架构为7.5,Ampere架构为8.6
export CUDA_ARCH=75
export TC_USE_DOUBLE=1

然后就可以运行scripts目录中的例子了,需要注意这里的例子都需要较大的内存才能运行,结果文件会被保存到taichi/outputs目录下

6. 基本使用方法

由于没有详细的说明文档,使用的方法主要可以参考scripts目录下的相关实例脚本,同时编写的脚本也需要放置于scripts目录下。

(1) 导出模型

该代码隐含要求设计域为(-0.5, -0.5, -0,5)到(0.5, 0.5, 0.5)组成体积为1的立方体区域,为此在导入设计模型是需要将设计的三维模型缩放至在导出模型的坐标系中刚好在该包络内,导出模型的格式为Wavefront的ojb格式,导出方式类似stl并且需要在导出时指定坐标系。导出的模型放置于data目录下。

(2) 参数设置

求解主要的参数为网格的分辨率和体积分数,另外一般可以开启保留位移边界和载荷施加位置的单元的设置,其他参数的设置可参考其他实例脚本中的设置

from topo_opt import TopoOpt;
version = 1;
narrow_band = True;
#体积分数
volume_fraction = 0.1;
#网格分辨率
n = 3000;
#fix_cells_at_dirichlet和fix_cells_near_force分别为保留位移边界和载荷施加位置的单元
opt = TopoOpt(res=(n, n, n), version=version, volume_fraction=volume_fraction,
              grid_update_start=5 if narrow_band else 1000000,
              fix_cells_near_force=True, fix_cells_at_dirichlet=True, fixed_cell_density=0.1);

(3) 施加边界条件

通过add_dirichlet_bc和add_load及其他的衍生方法施加位移及载荷边界条件,目前支持以点为中心的区域,平面区域,长方体区域,网格表面等施加方法。

#施加位移边界条件
opt.add_dirichlet_bc((0, 0, -0.1), radius=0.05, axis='xyz', value=(0, 0, 0));
#施加载荷边界条件
opt.add_load(center=(0.0, -y + 0.01, 0.0), force=(0, -1e6, 0));

(4) 结果可视化

计算的结果将保存到taichi/outputs目录下,使用ti vd命令可以实现对计算结果的可视化,需要配置好CUDA支持才可以使用

#对fem目录下的结果文件进行可视化显示
ti vd fem

使用1,2,3键可以对结果进行对三个坐标轴的对称显示,H和L键可以在多个求解结果文件之间进行切换,J和K键可调节显示部分的最小密度,T键可进行透明化显示(类似X光片),S及Shift+S可对可视化结果进行保存(保存为png或是ply格式到/tmp目录下)。