菜鸡的selenium之旅

需求

(注:以下举例不包含任何恶意行为,如有不适请予以指正)

前几天帮朋友写一个爬虫,给出一些公司的code和公司高管姓名,要求爬取对应高管的一些公开信息,我们打开新浪财经网,先手动操作这个过程,首先是首页搜索框输入公司代码如000021

home_page

然后打开一个新的页面,这里我们点击进入公司页面

search_page

这里公司界面左下方我们找到公司高管的链接

com_page

之后进入公司高管页面

ceo_page

再之后就是搜索到对应的姓名点击链接进入并搜集需要的信息

初次实现

起初是打算用urllib之类的很底层的库来模拟get和post之类的过程而不打算使用浏览器,后来看网站的网络请求过程比较复杂于是就入坑了selenium

第一次使用selenium,由于是在家里只能百度搜索(吐槽下百度各种广告和搜不到)还有各种原因看不了官网文档,于是跟着网上的大部分博客写了个简单粗暴的版本

首先selenium的浏览器可以选择phantomjs,chrome,firefox,这里由于不想折腾太多环境问题,我就选择了chrome

环境配置

首先查看chrome版本,在浏览器标签栏输入chrome://version查看浏览器版本信息

chrome_version

然后对应如下表格找到对应chromeDriver版本下载安装

chromeDriver版本 chrome版本
v2.43 v69-71
v2.42 v68-70
v2.41 v67-69
v2.40 v66-68
v2.39 v66-68
v2.38 v65-67
v2.37 v64-66
v2.36 v63-65
v2.35 v62-64
v2.33 v60-62
v2.32 v59-61
v2.31 v58-60
v2.30 v58-60
v2.29 v56-58
v2.28 v55-57
v2.27 v54-56
v2.26 v53-55
v2.25 v53-55
v2.24 v52-54
v2.23 v51-53
v2.22 v49-52
v2.21 v46-50
v2.20 v43-48
v2.19 v43-47
v2.18 v43-46
v2.17 v42-43
v2.13 v42-45
v2.15 v40-43
v2.14 v39-42
v2.13 v38-41
v2.12 v36-40
v2.11 v36-40
v2.10 v33-36
v2.9 v31-34
v2.8 v30-33
v2.7 v30-33
v2.6 v29-32
v2.5 v29-32
v2.4 v29-32

这边是chromedriver的链接

接下来安装selenium(我的机子python2和3都装了,只装了3的可以只敲pip)

1
pip3 install selenium

初次搬砖

selenium的API参考官方说明

这边列举下常用接口(转自https://www.cnblogs.com/yufeihlf/p/5764807.html)

可用driver:

  • phantomjs
  • chrome
  • firefox

常用变量:

  • driver.current_url:用于获得当前页面的URL
  • driver.title:用于获取当前页面的标题
  • driver.page_source:用于获取页面html源代码
  • driver.current_window_handle:用于获取当前窗口句柄
  • driver.window_handles:用于获取所有窗口句柄

常用函数:

  • driver.find_element*():定位元素
  • driver.get(url):浏览器加载url。
    实例:driver.get(“http//:www.baidu.com")
  • driver.forward():浏览器向前(点击向前按钮)。
  • driver.back():浏览器向后(点击向后按钮)。
  • driver.refresh():浏览器刷新(点击刷新按钮)。
  • driver.close():关闭当前窗口,或最后打开的窗口。
  • driver.quit():关闭所有关联窗口,并且安全关闭session。
  • driver.maximize_window():最大化浏览器窗口。
  • driver.set_window_size(宽,高):设置浏览器窗口大小。
  • driver.get_window_size():获取当前窗口的长和宽。
  • driver.get_window_position():获取当前窗口坐标。
  • driver.get_screenshot_as_file(filename):截取当前窗口。
    实例:driver.get_screenshot_as_file(‘D:/selenium/image/baidu.jpg’)
  • driver.implicitly_wait(秒):隐式等待,通过一定的时长等待页面上某一元素加载完成。
    若提前定位到元素,则继续执行。若超过时间未加载出,则抛出NoSuchElementException异常。
    实例:driver.implicitly_wait(10) #等待10秒
  • driver.switch_to_frame(id或name属性值):切换到新表单(同一窗口)。若无id或属性值,可先通过xpath定位到iframe,再将值传给switch_to_frame()
  • driver.switch_to.parent_content():跳出当前一级表单。该方法默认对应于离它最近的switch_to.frame()方法。
  • driver.switch_to.default_content():跳回最外层的页面。
  • driver.switch_to_window(窗口句柄):切换到新窗口。
  • driver.switch_to.window(窗口句柄):切换到新窗口。
  • driver.switch_to_alert():警告框处理。处理JavaScript所生成的alert,confirm,prompt.
  • driver.switch_to.alert():警告框处理。
  • driver.execute_script(js):调用js。
  • driver.get_cookies():获取当前会话所有cookie信息。
  • driver.get_cookie(cookie_name):返回字典的key为“cookie_name”的cookie信息。
    实例:driver.get_cookie(“NET_SessionId”)
  • driver.add_cookie(cookie_dict):添加cookie。“cookie_dict”指字典对象,必须有name和value值。
  • driver.delete_cookie(name,optionsString):删除cookie信息。
  • driver.delete_all_cookies():删除所有cookie信息。

至于元素的搜索有tag_name,xpath,id等多种方式,这里不一一列举,详见文档

首先是浏览器的打开,这里用的是chrome引擎

1
2
3
browser = webdriver.Chrome()
url = 'https://finance.sina.com.cn/'
browser.get(url)

然后是关于超时加载的问题,这里我由于网速慢我设置了较长的等待时间等到一些元素加载完再执行js脚本让浏览器停止加载全部页面

1
2
3
4
5
6
7
8
def openHomePage(browser,url):
browser.set_page_load_timeout(240)
try:
browser.get(url)
except TimeoutException:
print('stop load')
browser.execute_script('window.stop ? window.stop() : document.execCommand("Stop");') #执行js脚本
return None

之后就是按要求搜索到对应html元素并且执行对应操作,,就是各种find_element然后click或者send_key,然后控制一下sleep来等待加载网页元素防止报错元素不存在即还未加载完这里就不一一贴代码了,后面会放代码链接

这里有一个用于切换tab页面的可以看一下

1
2
3
4
5
6
7
8
9
10
11
12
def switch_handle(handles,current_handle):
for handle in handles:
if handle != current_handle:
return handle

def switch_page(current_handle,handles,new_handle,browser):
current_handle = browser.current_window_handle
handles = browser.window_handles
new_handle = switch_handle(handles,current_handle)
browser.close()
browser.switch_to.window(new_handle)
return None

第一次搬砖过程中由于是第一次入坑selenium而且有点小偷懒,于是GUI也没关,就用上面那个步骤把输入文件读入后for了一下暴力解决,结果显而易见龟速爬虫,于是后面朋友还是类似操作的爬虫要求我进行了优化

进行优化

由于初次搬砖结果一般般,然后我开始考虑优化,首先一个是使用了GUI然后速度太慢,第二个由于等待加载过程什么也没做cpu空闲着可以考虑用多线程优化,第三个通过分析网页链接可以知道公司高管界面的url有一定规律,直接get这个界面省去前面的操作

关于无头浏览器,一开始我是去直接安装phantomjs但是遇到各种坑后面发现selenium后面不支持phantomjs了,有些接口是没维护的,于是后来使用了chrome的无头模式,也不会慢多少

无头模式可以在生成driver对象的时候填入chrome_options进行设置,下面代码顺便设置了一些service_args用来禁用一些图片加载和忽略一些网络错误以及启用浏览器cache

1
2
3
4
chrome_options = Options()
chrome_options.add_argument("--headless")
service_args = ['--ignore-ssl-errors=true','--ssl-protocol=TLSv1','--load-images=no','--disk-cache=yes']
browser = webdriver.Chrome(service_args=service_args,chrome_options=chrome_options)

关于多线程,这部分经历了一定的思考过程,由于不同线程之间的互斥访问资源,最后想到使用循环队列来解决这个问题,这里我写了一个类用来实现这部分功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class conChrome:
chrome_max = 1 # 最大线程数
interval = 0.00001 # 两个线程之间创建时间间隔
timeout = 240 # 设置超时时间
chrome_options = Options()
service_args = ['--ignore-ssl-errors=true','--ssl-protocol=TLSv1','--load-images=no','--disk-cache=yes']

def __init__(self):
self.q_chrome = queue.Queue() #chromeDriver队列
self.chrome_options.add_argument("--headless")
self.q_output = queue.Queue() #偷懒用来存储输出
self.q_error = queue.Queue() #偷懒用来存储error

# 多线程创建driver
def open_chrome(self):
def open_threading():
browser = webdriver.Chrome(service_args=conChrome.service_args,chrome_options=conChrome.chrome_options)
browser.implicitly_wait(conChrome.timeout)
browser.set_page_load_timeout(conChrome.timeout)
self.q_chrome.put(browser) # 往队列末尾添加driver

th = []
for i in range(conChrome.chrome_max):
t = threading.Thread(target=open_threading)
th.append(t)
for i in th:
i.start()
time.sleep(conChrome.interval) #设置两个线程创建时间间隔
for i in th:
i.join()

# 多线程关闭chrome
def close_chrome(self):
th = []
def close_threading():
browser = self.q_chrome.get()
browser.quit()
for i in range(self.q_chrome.qsize()):
t = threading.Thread(target=close_threading)
th.append(t)
for i in th:
i.start()
for i in th:
i.join()

然后在类里面写一个getPage函数,get一个driver出队列,操作完put回去队列尾部,具体代码就不贴全部了给关键操作后面有链接

1
2
3
4
5
def getPage(url):
browser = self.q_chrome.get()
browser.get(url)
... # 一些爬虫操作抓取信息
self.q_chrome.put(browser)

最后是main里面创建多线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
cur = conChrome()
conChrome.chrome_max = 3
cur.open_chrome()
print ('chrome num is ',cur.q_chrome.qsize())
th = []
for i in input:
t = threading.Thread(target=cur.getPage,args=(i,))
th.append(t)
for i in th:
i.start()
for i in th:
i.join()

cur.close_chrome()

源码链接

关于dom的事件传播机制

关于dom的事件传播机制

场景: Vue项目里点击一个菜品列表的一个item进去详细信息页,在点击item内部的加号按钮时同时触发了加号按钮监听的事件和点击item跳转的事件
解决方案:可以利用dom的事件流原理解决

关于dom的事件流

dom的事件流有两种模式,冒泡和捕获

  • 冒泡模式:冒泡模式下事件从触发的element开始逐层往父节点传递
  • 捕获模式:捕获模式顺序和冒泡模式反过来

由于历史原因现在默认是冒泡模式,要更改模式可以调用addEventListener接口把最后一个参数赋值true就行

具体实现

在我的项目中父对象是item子对象是i标签,大概如下的结构

1
2
3
<item>
<i></i>
</item>

可以在父对象监听器调用的函数判断event的触发者

1
2
3
4
5
6
7
8
9
10
viewDetailedDish (d, event) {
console.log(event.target.tagName)
if (event.target.tagName === 'I') {
console.log(this.dishes)
console.log('prevent')
} else {
this.setDetailDish(d)
this.$router.push({name: 'Details'})
}
}

finalReport

简短的课程学习自我总结(400字以内)

  • 请不用讲述与分析、设计、开发、管理无关的话题
  • 可以包括对同学帮助的致谢(同学姓名请用 github 昵称表示,不许出现真实姓名)
  • 0 ~ 400字,即没有这段也没关系

在这次项目中担任了前端工程师和产品经理的任务吧,前期主要是负责分配各种文档给谁写的任务,编码过程中主要负责手机端的前端Vue代码编写以及和后端沟通接口问题,总的来说有些小成长吧,第一次担任管理类的角色总感觉有些力不从心(其实感觉个人更适合编码,毕竟后期主要都是在搬前端的砖),管理方面的话感觉逐渐感受到沟通还有项目规划的重要性吧,编码方面这次前端搬砖搬了挺多的吧,感觉加深了对MVC架构的理解,设计方面的话实际上就是参考框架和上课老师讲的一些设计模式设计的(我们用Django很自然地分了模块),至于分析方面,其实需求分析能力感觉个人还应该提升

PSP 2.1 统计表

PSP2.1 Personal Software Process Stages Time(%)
Planning 计划 5
Estimate 估计这个任务需要多少时间 5
Development 开发 85
Analysis 市场调研,需求分析 8
Design Spec 生成设计文档 8
Design Review 设计复审 5
Coding Standard 代码规范 2
Design 具体设计 17
Coding 具体编码 35
Code Review 代码复审 2
Test 测试 8
Reporting 报告 10
Test Report 测试报告 4
Size MeaSurement 计算工作量 3
Postmortem&Process Improvement Plan 时候总结,并提出过程改进计划 3

个人分支的 GIT 统计报告(不需要解释原因)- 仅需要提交截图

  • WebOrder

WebOrder

具体的如下
WebOrder
WebOrder
WebOrder
WebOrder
WebOrder
WebOrder
WebOrder
WebOrder
WebOrder
WebOrder
WebOrder
WebOrder
WebOrder
WebOrder
WebOrder
WebOrder
WebOrder

  • CookEnd

CookEnd
具体如下
CookEnd

  • dashboard

dashboard

自认为最得意/或有价值/或有苦劳的工作清单,含简短说明(一句话)

  • 前期一些文档的编写,比如用例图,一些ssd之类的
  • 前期编写了WebOrder手机端的文件目录
  • 一些必要的开会沟通?(有时候群里交流说不清然后就一起到文化室)
  • 后期一些前端接口与后端对接,假微信登录,假支付接口,提交订单接口,获取菜品接口,获取历史订单接口,评价历史订单接口
  • 后期把之前的直男审美UI设计调整成目前的手机端界面,做了挺多css以及vue组件或者一些开源组件使用的调整,比如主页面,支付弹框,用户信息页,历史订单页,假微信登录页
  • 前端bug修复,一些点击事件问题,一些接口调用问题,浏览器跨域请求问题等等(在对接接口过程中出现不少问题踩了不少坑,后期优化ui还有前端一些bug也是在做着)
  • 将前端手机端文件build成静态资源后在服务器上部署ngnix定位资源以及前端api时的反向代理时编写conf文件和配置ngnix解决环境问题

个人的技术类、项目管理类博客清单(只需要名称与 url )

关于项目管理的一些小感想

作为团队的产品经理加前端工程师??

这次系分组队组了个直男团队,一开始也没有人愿意做管理这块的工作,然后就分锅咯,最后产品经理的活落我这里,也是第一次做这种工作感觉做得不是很合格,跟其他项目的做管理的妹子请教过她说最重要的是规划和人格魅力,就有的时候在群里喊干活了或者讨论一下然后群里没有应答,而且任务分配这一块也是,还有需求分析等等,一些队友的实习工作甩锅之类的问题,后期编码阶段我是前端这边然后大家水平也不一样然后就各种补锅之类的问题,最后在各位队友的合作下也算解决了吧,总得来说有下面几点有助于管理吧。

有助于管理项目的几个点

  • 面向ddl开发,看板一定要用,有了ddl团队才不会有惰性
  • 沟通很重要,前后端接口之间,领域模型设计,关于项目用例的看法之类的,要及时沟通,这次项目在接口对接的时候由于后端大佬把文档写在后端仓库里面而前端开发以为是在dashboard仓库那个导致后来接口对接的时候出现挺大问题,总之在每个系统顺序图(ssd)出来的时候应该及时进行两边的沟通,其他文档提交后也同理要这么做
  • 对一个新的功能需求要准确评估代价,在项目提交前我们加了个用例然后也是花了挺长时间写的,我认为这个十分必要
  • 最后是任务分配问题,开会分配任务吧,面对面沟通完把任务分配完贴到看板上我觉得比较有效率。

post 403

前端psot返回403(CSRF Failed: CSRF token missing or incorrect)

情景:在Vue项目中用axios发送post请求返回标题所述的403
原因:这是OAuth 2.0中为了防止第三方网站攻击加上的字段,从document.cookie里面将对应字段取出加在request header里发出post即可
实现代码如下

1
2
3
4
5
6
7
var getCookie = (name) => {
let value = '; ' + document.cookie
let parts = value.split('; ' + name + '=')
if (parts.length === 2) return parts.pop().split(';').shift()
}
axios.post('/api/wxLogin/', params, {headers: {'X-CSRFToken': getCookie('csrftoken')}})
//差不多就这么写把后面的回调函数就不贴了

系分项目运维日记

运维日记

在代理服务器上配置ngnix服务器反向代理转发API请求遇到如下两个坑

  • 安装ngnix时遇到一些依赖问题
  • vue router history mode在非服务器根目录下conf的配置
    下载安装包
    首先去ngnix官网下载安装包,可以用curl或wget直接下到服务器上
    1
    wget http://nginx.org/download/nginx-1.9.9.tar.gz

解压

1
tar -zxvf nginx-1.9.9.tar.gz

然后先进入解压好的文件夹内编译安装

1
2
cd nginx-1.9.9/
./configure

这时候还没make编译但是报了个少pcre的错误
如下

1
2
3
4
./configure: error: the HTTP rewrite module requires the PCRE library.
You can either disable the module by using --without-http_rewrite_module
option, or install the PCRE library into the system, or build the PCRE library
statically from the source with nginx by using --with-pcre=<path> option.

然后去pcre官网下安装包,同理用wget或者curl下载到服务器上,然后解压安装,这是官网链接https://ftp.pcre.org/pub/pcre/
同理解压后安装如下

1
2
3
4
5
cd pcre-8.42/
./configure --prefix=/usr/local/pcre
make
make check
sudo make install

上面的check选项用于测试是否编译出错
安装完毕后重新安装ngnix

1
./configure --prefix=/usr/local/nginx  --with-pcre=/usr/local/pcre/

这个时候仍然缺少zlib库

1
2
3
4
./configure: error: the HTTP gzip module requires the zlib library.
You can either disable the module by using --without-http_gzip_module
option, or install the zlib library into the system, or build the zlib library
statically from the source with nginx by using --with-zlib=<path> option.

直接用apt安装

1
2
sudo apt install zlib1g
sudo apt install zlib1g-dev

然后搜了一下资料把其他相关依赖也装了

1
sudo apt install libpcre3 libpcre3-dev libpcrecpp0v5 libssl-dev

之后安装ngnix

1
2
3
./configure
make
sudo make install

去安全组那边开放80端口

Alt text

然后去ngnix目录启动ngnix

1
2
cd /usr/local/ngnix/sbin
sudo ./ngnix

关于Vue的history mode在配置ngnix时的一些坑

vue router默认是hash模式,但是我们在项目中用url的params来确定桌子号故必须用history模式,一开始我按照官网教程编译完直接使用try_files指令配置ngnix但是一直找不到资源,后来翻国外论坛看到了,大概如下配置
首先要更改Vue Router的配置的base路径(下面的/cloud/路径即为服务器中该项目前端资源的路径)

1
2
base: '/cloud/',
// base: __dirname,

然后更改config/index.js里的配置文件

1
2
assetsPublicPath: '/cloud/', 
// assetsPublicPath: '/',

最后在ngnix的配置文件中使用alias命令定位资源

1
2
3
4
5
6
7
location ^~ /cloud/ {
alias /home/ubuntu/EasyOrderMeal/WebOrder/dist/;
index index.html index.htm;
try_files $uri $uri/ /cloud/index.html;
#root html;
#proxy_pass http://localhost:8080;
}

HomeworkLesson16

1.使用 ECB 实现 make reservation 用例的详细设计(包含用例简介,顺序图,类图)

  • 用例简介

usercase

usercase

  • 顺序图

sequence

  • 类图

class

2.将逻辑设计类图映射到实际项目框架的包图。用树形结构表述实现的包和类

code

HomeworkLesson13

1.描述软件架构与框架之间的区别与联系

软件架构

  • 定义:软件架构就是把系统分解为一些部件,描述这些部件的职责及它们之间的协作行为。
  • 特点
    • 软件结构是业界长期最佳实践的总结。
    • 软件架构是对系统的一种理解,与具体语言无关。

应用程序框架

  • 定义:框架是特定语言和技术的架构应用解决方案。例如 Java Spring web framework,它包含了 Java 开发 web 应用的各种业务场景的具体解决方案。
  • 特点
    • 框架是具体语言和技术相关的。
    • 框架是一种或多种架构的组合的实现。
    • 框架是集成了你的代码和多种第三方解决方案的工具,让你聚焦 业务逻辑代码 而 不是技术实现。

2.以你的项目为案例

  • 绘制三层架构模型图,细致到分区
  • 结合你程序的结构,从程序员角度说明三层架构给开发者带来的便利

三层模型图如下图所示
order_arch

  • 对于负责表示层的程序员,他只需关注MVC模型,处理 HTTP Request,然后调用业务服务,产生Response,而不需关注其他两层的编程元素
  • 对于负责业务层的程序员,他只需提供满足各个业务的服务模块,以满足restfulAPI规范的接口
  • 对于负责持久化层的程序员,他只需关注数据库机制,提供ORM支持返回数据对象供业务层调用

3.研究 VUE 与 Flux 状态管理的异同

对于Flux,可以参考官网的图

flux

对于VUE,提供状态管理机制的主要是vuex,这里可以看官网给出的图

vuex

下面是对于flux和vuex异同的说明

  • flux : 一个 Model 可以被多个 Views 读取或被多个 Controllers 进行更新。在大型应用中,一个 Model 可能使多个 Views 去通知 Controllers,并可能触发更多的 Model 更新,这样结果就会变得非常复杂。Flux 通过强制单向数据流来解决这个额问题。Flux 使 Views 查询 Stores(而不是 Models),用户交互触发的 Actions 被提交到一个 Dispatcher 中。当 Actions 被派发后,Stores 将会随之更新自己并且通知 Views 进行修改。这些 Store 当中的修改会进一步促使 Views 查询新的数据。即在相对独立的组件中,action -> state -> view 的单向数据流能得到保证。
  • vuex : Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
    • 应用级的状态由store集中管理
    • 修改状态的唯一方式是commit同步的mutation
    • 异步逻辑放在action里
  • 总结:两者相比,vuex只有一个store(即vuex对象),而flux少了vuex通过action和mutation把异步与同步区别开来,vuex由store自身充当dispatcher(即view触发的动作都由vuex来注册/分发action和mutation)。

HomeworkLesson8

1)使用 UML State Model

  • 建模对象: 参考 Asg_RH 文档, 对 Reservation/Order 对象建模。
  • 建模要求: 参考练习不能提供足够信息帮助你对订单对象建模,请参考现在 定旅馆 的旅游网站,尽可能分析围绕订单发生的各种情况,直到订单通过销售事件(柜台销售)结束订单。

如下图所示:
order

2)研究淘宝退货流程活动图,对退货业务对象状态建模

如下图所示:
taobao