1 爬虫高级
1.1 动态HTML处理和机器图像识别
爬虫(Spider),反爬虫(Anti-Spider),反反爬虫(Anti-Anti-Spider) 之间恢宏壮阔的斗争...
Day 1
· 小莫想要某站上所有的电影,写了标准的爬虫(基于HttpClient库),不断地遍历某站的电影列表页面,根据 Html 分析电影名字存进自己的数据库。
· 这个站点的运维小黎发现某个时间段请求量陡增,分析日志发现都是 IP(xxx.xxx.xxx.xxx)这个用户,并且 user-agent 还是 Python-urllib/2.7 ,基于这两点判断非人类后直接在服务器上封杀。
Day 2
· 小莫电影只爬了一半,于是也针对性的变换了下策略:1. user-agent 模仿百度("Baiduspider..."),2. IP每爬半个小时就换一个IP代理。
· 小黎也发现了对应的变化,于是在服务器上设置了一个频率限制,每分钟超过120次请求的再屏蔽IP。 同时考虑到百度家的爬虫有可能会被误伤,想想市场部门每月几十万的投放,于是写了个脚本,通过 hostname 检查下这个 ip 是不是真的百度家的,对这些 ip 设置一个白名单。
Day 3
· 小莫发现了新的限制后,想着我也不急着要这些数据,留给服务器慢慢爬吧,于是修改了代码,随机1-3秒爬一次,爬10次休息10秒,每天只在8-12,18-20点爬,隔几天还休息一下。
· 小黎看着新的日志头都大了,再设定规则不小心会误伤真实用户,于是准备换了一个思路,当3个小时的总请求超过50次的时候弹出一个验证码弹框,没有准确正确输入的话就把 IP 记录进黑名单。
Day 4
· 小莫看到验证码有些傻脸了,不过也不是没有办法,先去学习了图像识别(关键词 PIL,tesseract),再对验证码进行了二值化,分词,模式训练之后,总之最后识别了小黎的验证码(关于验证码,验证码的识别,验证码的反识别也是一个恢弘壮丽的斗争史...),之后爬虫又跑了起来。
· 小黎是个不折不挠的好同学,看到验证码被攻破后,和开发同学商量了变化下开发模式,数据并不再直接渲染,而是由前端同学异步获取,并且通过 JavaScript 的加密库生成动态的 token,同时加密库再进行混淆(比较重要的步骤的确有网站这样做,参见淘宝和微博的登陆流程)。
Day 5
· 混淆过的加密库就没有办法了么?当然不是,可以慢慢调试,找到加密原理,不过小莫不准备用这么耗时耗力的方法,他放弃了基于 HttpClient的爬虫,选择了内置浏览器引擎的爬虫(关键词:PhantomJS,Selenium),在浏览器引擎运行页面,直接获取了正确的结果,又一次拿到了对方的数据。
· 小黎:.....
爬虫与发爬虫的斗争还在继续...
通常情况下,在爬虫与反爬虫的对弈中,爬虫一定会胜利。
换言之,只要人类能够正常访问的网页,爬虫在具备同等资源
的情况下就一定可以抓取到。
关于爬虫部分一些建议:
尽量减少请求次数,能抓列表页就不抓详情页,减轻服务器压力,程序员都是混口饭吃不容易。
不要只看 Web 网站,还有手机 App 和 H5,这样的反爬虫措施一般比较少。
实际应用时候,一般防守方做到根据 IP 限制频次就结束了,除非很核心的数据,不会再进行更多的验证,毕竟成本的问题会考虑到。
如果真的对性能要求很高,可以考虑多线程(一些成熟的框架如 Scrapy都已支持),甚至分布式...
关于反爬虫部分的一些建议:
· 这篇文章就够了:携程技术中心 - 携程酒店研发部研发经理崔广宇 <爬虫与反爬虫> 技术分享
1.1.1 动态HTML介绍
JavaScript
JavaScript 是网络上最常用也是支持者最多的客户端脚本语言。它可以收集 用户的跟踪数据,不需要重载页面直接提交表单,在页面嵌入多媒体文件,甚至运行网页游戏。
我们可以在网页源代码的<scripy>
标签里看到,比如:
<script type=
jQuery
jQuery 是一个十分常见的库,70% 最流行的网站(约 200 万)和约 30% 的其他网站(约 2 亿)都在使用。一个网站使用 jQuery 的特征,就是源代码里包含了 jQuery 入口,比如:
<script type=
如果你在一个网站上看到了 jQuery,那么采集这个网站数据的时候要格外小心。jQuery 可 以动态地创建 HTML 内容,只有在 JavaScript 代码执行之后才会显示。如果你用传统的方 法采集页面内容,就只能获得 JavaScript 代码执行之前页面上的内容。
Ajax
我们与网站服务器通信的唯一方式,就是发出 HTTP 请求获取新页面。如果提交表单之后,或从服务器获取信息之后,网站的页面不需要重新刷新,那么你访问的网站就在用Ajax 技术。
Ajax 其实并不是一门语言,而是用来完成网络任务(可以认为 它与网络数据采集差不多)的一系列技术。Ajax 全称是 Asynchronous JavaScript and XML(异步 JavaScript 和 XML),网站不需要使用单独的页面请求就可以和网络服务器进行交互 (收发信息)。
DHTML
Ajax 一样,动态 HTML(Dynamic HTML, DHTML)也是一系列用于解决网络问题的 技术集合。DHTML 是用客户端语言改变页面的 HTML 元素(HTML、CSS,或者二者皆 被改变)。比如页面上的按钮只有当用户移动鼠标之后才出现,背景色可能每次点击都会改变,或者用一个 Ajax 请求触发页面加载一段新内容,网页是否属于DHTML,关键要看有没有用 JavaScript 控制 HTML 和 CSS 元素。
那么,如何搞定?
那些使用了 Ajax 或 DHTML 技术改变 / 加载内容的页面,可能有一些采集手段。但是用 Python 解决这个问题只有两种途径:
直接从 JavaScript 代码里采集内容(费时费力)
用 Python 的 第三方库运行 JavaScript,直接采集你在浏览器里看到的页面(这个可以有)。
1.1.2 Selenium与PhantomJS
Selenium
Selenium是一个Web的自动化测试工具,最初是为网站自动化测试而开发的,类型像我们玩游戏用的按键精灵,可以按指定的命令自动操作,不同是Selenium 可以直接运行在浏览器上,它支持所有主流的浏览器(包括PhantomJS这些无界面的浏览器)。
Selenium 可以根据我们的指令,让浏览器自动加载页面,获取需要的数据,甚至页面截屏,或者判断网站上某些动作是否发生。
Selenium 自己不带浏览器,不支持浏览器的功能,它需要与第三方浏览器结合在一起才能使用。但是我们有时候需要让它内嵌在代码中运行,所以我们可以用一个叫 PhantomJS 的工具代替真实的浏览器。
可以从 PyPI 网站下载 Selenium库https://pypi.python.org/simple/selenium ,
也可以用 第三方管理器 pip用命令安装:sudo pip install selenium
Selenium 官方参考文档:http://selenium-python.readthedocs.io/index.html
PhantomJS
PhantomJS 是一个基于Webkit的“无界面”(headless)浏览器,它会把网站加载到内存并执行页面上的 JavaScript,因为不会展示图形界面,所以运行起来比完整的浏览器要高效。
如果我们把 Selenium 和 PhantomJS 结合在一起,就可以运行一个非常强大的网络爬虫了,这个爬虫可以处理 JavaScrip、Cookie、headers,以及任何我们真实用户需要做的事情。
PhantomJS 是一个功能完善(虽然无界面)的浏览器而非一个 Python 库,所以它不需要像 Python 的其他库一样安装,但我们可以通过Selenium调用PhantomJS来直接使用。
PhantomJS 官方参考文档:http://phantomjs.org/documentation
安装示例1:
- 下载PhantomJS:
<pre style="background:#F7F7F7">wget https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-linux-x86_64.tar.bz2</pre>
- 解压并创建软连接:
tar -xvjf phantomjs-2.1.1-linux-x86_64.tar.bz2
sudo cp -R phantomjs-2.1.1-linux-x86_64 /usr/local/share/
sudo ln -sf /usr/local/share/phantomjs-2.1.1-linux-x86_64/bin/phantomjs /usr/local/bin/
安装实例2:
|
1、下载安装
sudo apt update
sudo apt install phantomjs
2、下载driver
http://chromedriver.storage.googleapis.com/index.html
3、修改权限、移动
|
<v:shapetype id="_x0000_t75" coordsize="21600,21600" o:spt="75" o:preferrelative="t" path="m@4@5l@4@11@9@11@9@5xe" filled="f" stroked="f"><v:stroke joinstyle="miter"><v:formulas></v:formulas><v:path o:extrusionok="f" gradientshapeok="t" o:connecttype="rect"></v:path></v:stroke></v:shapetype><v:shape id="图片_x0020_44" o:spid="_x0000_i1035" type="#_x0000_t75" style="width:223.8pt;height:303.6pt;visibility:visible;mso-wrap-style:square"><v:imagedata src="file:///C:/Users/10843/AppData/Local/Temp/msohtmlclip1/01/clip_image001.png" o:title=""></v:imagedata></v:shape>
ubuntu16.04 chrome浏览器安装方式
https://jingyan.baidu.com/article/335530da98061b19cb41c31d.html
快速入门
Selenium 库里有个叫 WebDriver 的 API。WebDriver 有点儿像可以加载网站的浏览器,但是它也可以像 BeautifulSoup 或者其他 Selector 对象一样用来查找页面元素,与页面上的元素进行交互 (发送文本、点击等),以及执行其他动作来运行网络爬虫。
<pre style="background:#F7F7F7"># IPython测试代码</pre>
<pre style="background:#F7F7F7"># 导入 webdriver</pre>
<pre style="background:#F7F7F7">from selenium
import webdriver
</pre>
<pre style="background:#F7F7F7"># 调用环境变量指定的PhantomJS浏览器创建浏览器对象</pre>
driver = webdriver.PhantomJS()
<pre style="background:#F7F7F7"># 如果没有在环境变量指定PhantomJS位置</pre>
<pre style="background:#F7F7F7"># driver = webdriver.PhantomJS(executable_path="/usr/local/bin/phantomjs"))</pre>
<pre style="background:#F7F7F7"># get方法会一直等到页面被完全加载,然后才会继续程序,通常测试会在这里选择 time.sleep(2)</pre>
driver.get(
<pre style="background:#F7F7F7"># 获取页面名为 wrapper的id标签的文本内容</pre>
data = driver.find_element_by_id(
<pre style="background:#F7F7F7"># 打印数据内容</pre>
<pre style="background:#F7F7F7">print(data)
</pre>
<pre style="background:#F7F7F7"># 打印页面标题 "百度一下,你就知道"</pre>
<pre style="background:#F7F7F7">print(driver.title)
</pre>
<pre style="background:#F7F7F7"># 生成当前页面快照并保存</pre>
driver.save_screenshot(
<pre style="background:#F7F7F7"># id="kw"是百度搜索输入框,输入字符串"长城"</pre>
driver.find_element_by_id(
<pre style="background:#F7F7F7"># id="su"是百度搜索按钮,click() 是模拟点击</pre>
driver.find_element_by_id(
<pre style="background:#F7F7F7"># 获取新的页面快照</pre>
driver.save_screenshot(
<pre style="background:#F7F7F7"># 打印网页渲染后的源代码</pre>
<pre style="background:#F7F7F7">print(driver.page_source)
</pre>
<pre style="background:#F7F7F7"># 获取当前页面Cookie</pre>
<pre style="background:#F7F7F7">print(driver.get_cookies())
</pre>
<pre style="background:#F7F7F7"># 调用键盘按键操作时需要引入的Keys包</pre>
<pre style="background:#F7F7F7">from selenium.webdriver.common.keys
import Keys
</pre>
<pre style="background:#F7F7F7"># ctrl+a 全选输入框内容</pre>
driver.find_element_by_id(
<pre style="background:#F7F7F7"># ctrl+x 剪切输入框内容</pre>
driver.find_element_by_id(
<pre style="background:#F7F7F7"># 输入框重新输入内容</pre>
driver.find_element_by_id(
<pre style="background:#F7F7F7"># 模拟Enter回车键</pre>
driver.find_element_by_id(
<pre style="background:#F7F7F7"># 清除输入框内容</pre>
driver.find_element_by_id(
<pre style="background:#F7F7F7"># 生成新的页面快照</pre>
driver.save_screenshot(
<pre style="background:#F7F7F7"># 获取当前url</pre>
<pre style="background:#F7F7F7">print(driver.current_url)
</pre>
<pre style="background:#F7F7F7"># 关闭当前页面,如果只有一个页面,会关闭浏览器</pre>
<pre style="background:#F7F7F7"># driver.close()</pre>
<pre style="background:#F7F7F7"># 关闭浏览器</pre>
driver.quit()
页面操作
Selenium 的 WebDriver提供了各种方法来寻找元素,假设下面有一个表单输入框:
<pre style="background:#F7F7F7"><input type="text" name="user-name" id="passwd-id" /></pre>
那么:
<pre style="background:#F7F7F7"># 获取id标签值</pre>
element = driver.find_element_by_id(
<pre style="background:#F7F7F7"># 获取name标签值</pre>
element = driver.find_element_by_name(
<pre style="background:#F7F7F7"># 获取标签名值</pre>
element = driver.find_elements_by_tag_name(
<pre style="background:#F7F7F7"># 也可以通过XPath来匹配</pre>
element = driver.find_element_by_xpath(
定位UI元素 (WebElements)
关于元素的选取,有如下的API 单个元素选取
find_element_by_id
find_elements_by_name
find_elements_by_xpath
find_elements_by_link_text
find_elements_by_partial_link_text
find_elements_by_tag_name
find_elements_by_class_name
find_elements_by_css_selector
- By ID
|
<div id="coolestWidgetEvah">...</div>
实现
element = driver.find_element_by_id("coolestWidgetEvah")
------------------------ or -------------------------
from selenium.webdriver.common.by import By
element = driver.find_element(by=By.ID, value="coolestWidgetEvah")
|
- By Class Name
|
<div class="cheese"><span>Cheddar</span></div><div class="cheese"><span>Gouda</span></div>
实现
cheeses = driver.find_elements_by_class_name("cheese")
------------------------ or -------------------------
from selenium.webdriver.common.by import By
cheeses = driver.find_elements(By.CLASS_NAME, "cheese")
|
- By Tag Name
|
<iframe src="..."></iframe>
实现
frame = driver.find_element_by_tag_name("iframe")
------------------------ or -------------------------
from selenium.webdriver.common.by import By
frame = driver.find_element(By.TAG_NAME, "iframe")
|
- By Name
|
<input name="cheese" type="text"/>
实现
cheese = driver.find_element_by_name("cheese")
------------------------ or -------------------------
from selenium.webdriver.common.by import By
cheese = driver.find_element(By.NAME, "cheese")
|
- By Link Text
|
<a href="http://www.google.com/search?q=cheese">cheese</a>
实现
cheese = driver.find_element_by_link_text("cheese")
------------------------ or -------------------------
from selenium.webdriver.common.by import By
cheese = driver.find_element(By.LINK_TEXT, "cheese")
|
- By Partial Link Text
|
<a href="http://www.google.com/search?q=cheese">search for cheese</a>>
实现
cheese = driver.find_element_by_partial_link_text("cheese")
------------------------ or -------------------------
from selenium.webdriver.common.by import By
cheese = driver.find_element(By.PARTIAL_LINK_TEXT, "cheese")
|
- By CSS
|
<div id="food"><span class="dairy">milk</span><span class="dairy aged">cheese</span></div>
实现
cheese = driver.find_element_by_css_selector("#food span.dairy.aged")
------------------------ or -------------------------
from selenium.webdriver.common.by import By
cheese = driver.find_element(By.CSS_SELECTOR, "#food span.dairy.aged")
|
- By XPath
|
<input type="text" name="example" />
<input type="text" name="other" />
实现
inputs = driver.find_elements_by_xpath("//input")
------------------------ or -------------------------
from selenium.webdriver.common.by import By
inputs = driver.find_elements(By.XPATH, "//input")
|
鼠标动作链
有些时候,我们需要再页面上模拟一些鼠标操作,比如双击、右击、拖拽甚至按住不动等,我们可以通过导入 ActionChains 类来做到:
示例:
<pre style="background:#F7F7F7">#导入 ActionChains 类</pre>
<pre style="background:#F7F7F7">from selenium.webdriver
import ActionChains
</pre>
<pre style="background:#F7F7F7"># 鼠标移动到 ac 位置</pre>
ac = driver.find_element_by_xpath(
ActionChains(driver).move_to_element(ac).perform()
<pre style="background:#F7F7F7"># 在 ac 位置单击</pre>
ac = driver.find_element_by_xpath(
ActionChains(driver).move_to_element(ac).click(ac).perform()
<pre style="background:#F7F7F7"># 在 ac 位置双击</pre>
ac = driver.find_element_by_xpath(
ActionChains(driver).move_to_element(ac).double_click(ac).perform()
<pre style="background:#F7F7F7"># 在 ac 位置右击</pre>
ac = driver.find_element_by_xpath(
ActionChains(driver).move_to_element(ac).context_click(ac).perform()
<pre style="background:#F7F7F7"># 在 ac 位置左键单击hold住</pre>
ac = driver.find_element_by_xpath(
ActionChains(driver).move_to_element(ac).click_and_hold(ac).perform()
<pre style="background:#F7F7F7"># 将 ac1 拖拽到 ac2 位置</pre>
ac1 = driver.find_element_by_xpath(
ac2 = driver.find_element_by_xpath(
ActionChains(driver).drag_and_drop(ac1, ac2).perform()
填充表单
我们已经知道了怎样向文本框中输入文字,但是有时候我们会碰到<select> </select>
标签的下拉框。直接点击下拉框中的选项不一定可行。
<pre style="background:#F7F7F7"><select id="status" class="form-control valid" onchange="" name="status"></pre>
<pre style="background:#F7F7F7"></select></pre>
<v:shape id="图片_x0020_25" o:spid="_x0000_i1034" type="#_x0000_t75" style="width:159pt;height:143.4pt;
visibility:visible;mso-wrap-style:square"><v:imagedata src="file:///C:/Users/10843/AppData/Local/Temp/msohtmlclip1/01/clip_image002.png" o:title=""></v:imagedata></v:shape>
Selenium专门提供了Select类来处理下拉框。 其实 WebDriver 中提供了一个叫 Select 的方法,可以帮助我们完成这些事情:
导入 Select 类
from selenium.webdriver.support.ui import Select
找到 name 的选项卡
select = Select(driver.find_element_by_name('status'))
select.select_by_index(1)
select.select_by_value("0")
select.select_by_visible_text("未审核")
以上是三种选择下拉框的方式,它可以根据索引来选择,可以根据值来选择,可以根据文字来选择。注意:
index 索引从 0 开始
value是option标签的一个属性值,并不是显示在下拉框中的值
visible_text是在option标签文本的值,是显示在下拉框的值
全部取消选择怎么办呢?很简单:
select.deselect_all()
弹窗处理
当你触发了某个事件之后,页面出现了弹窗提示,处理这个提示或者获取提示信息方法如下:
alert = driver.switch_to_alert()
页面切换
一个浏览器肯定会有很多窗口,所以我们肯定要有方法来实现窗口的切换。切换窗口的方法如下:
driver.switch_to.window(
也可以使用 window_handles 方法来获取每个窗口的操作对象。例如:
<pre style="background:#F7F7F7">for handle
in driver.window_handles:
</pre>
driver.switch_to_window(handle)
页面前进和后退
操作页面的前进和后退功能:
driver.forward()
driver.back()
Cookies
获取页面每个Cookies值,用法如下
<pre style="background:#F7F7F7">for cookie
in driver.get_cookies():
</pre>
删除Cookies,用法如下
<pre style="background:#F7F7F7"># By name</pre>
driver.delete_cookie(
<pre style="background:#F7F7F7"># all</pre>
driver.delete_all_cookies()
页面等待
现在的网页越来越多采用了 Ajax 技术,这样程序便不能确定何时某个元素完全加载出来了。如果实际页面等待时间过长导致某个dom元素还没出来,但是你的代码直接使用了这个WebElement,那么就会抛出NullPointer的异常。
为了避免这种元素定位困难而且会提高产生 ElementNotVisibleException 的概率。所以 Selenium 提供了两种等待方式,一种是隐式等待,一种是显式等待。
隐式等待是等待特定的时间,显式等待是指定某一条件直到这个条件成立时继续执行。
1.显式等待
显式等待指定某个条件,然后设置最长等待时间。如果在这个时间还没有找到元素,那么便会抛出异常了。
<pre style="background:#F7F7F7">from selenium
import webdriver
</pre>
<pre style="background:#F7F7F7">from selenium.webdriver.common.by
import By
</pre>
<pre style="background:#F7F7F7"># WebDriverWait 库,负责循环等待</pre>
<pre style="background:#F7F7F7">from selenium.webdriver.support.ui
import WebDriverWait
</pre>
<pre style="background:#F7F7F7"># expected_conditions 类,负责条件出发</pre>
<pre style="background:#F7F7F7">from selenium.webdriver.support
import expected_conditions
as EC
</pre>
driver = webdriver.PhantomJS()
driver.get(
<pre style="background:#F7F7F7">try:
</pre>
element = WebDriverWait(driver,
EC.presence_of_element_located((By.ID,
)
<pre style="background:#F7F7F7">finally:
</pre>
driver.quit()
如果不写参数,程序默认会 0.5s 调用一次来查看元素是否已经生成,如果本来元素就是存在的,那么会立即返回。
下面是一些内置的等待条件,你可以直接调用这些条件,而不用自己写某些等待条件了。
title_is
title_contains
presence_of_element_located
visibility_of_element_located
visibility_of
presence_of_all_elements_located
text_to_be_present_in_element
text_to_be_present_in_element_value
frame_to_be_available_and_switch_to_it
invisibility_of_element_located
element_to_be_clickable – it is Displayed and Enabled.
staleness_of
element_to_be_selected
element_located_to_be_selected
element_selection_state_to_be
element_located_selection_state_to_be
alert_is_present
2.隐式等待
隐式等待比较简单,就是简单地设置一个等待时间,单位为秒。
<pre style="background:#F7F7F7">from selenium
import webdriver
</pre>
driver = webdriver.PhantomJS()
driver.implicitly_wait(
driver.get(
myDynamicElement = driver.find_element_by_id(
当然如果不设置,默认等待时间为0。
1.1.3 案例一:网站模拟登录
<pre style="background:#F7F7F7"># -- coding:utf-8 -- </pre>
<pre style="background:#F7F7F7"># douban.py</pre>
<pre style="background:#F7F7F7">from selenium
import webdriver
</pre>
<pre style="background:#F7F7F7">from selenium.webdriver.common.keys
import Keys
</pre>
<pre style="background:#F7F7F7">import time
</pre>
driver = webdriver.PhantomJS()
driver.get(
<pre style="background:#F7F7F7"># 输入账号密码</pre>
driver.find_element_by_name(
driver.find_element_by_name(
<pre style="background:#F7F7F7"># 模拟点击登录</pre>
driver.find_element_by_xpath(
<pre style="background:#F7F7F7"># 等待3秒</pre>
time.sleep(
<pre style="background:#F7F7F7"># 生成登陆后快照</pre>
driver.save_screenshot(
<pre style="background:#F7F7F7"># 保存源码</pre>
<pre style="background:#F7F7F7">with open(
"douban.html",
"w")
as file:
</pre>
file.write(driver.page_source)
driver.quit()
1.1.4 案例二:动态页面模拟点击
爬取斗鱼直播平台的所有房间信息:
<pre style="background:#F7F7F7"># python的测试模块</pre>
<pre style="background:#F7F7F7">import unittest
</pre>
<pre style="background:#F7F7F7">from selenium
import webdriver
</pre>
<pre style="background:#F7F7F7">from bs4
import BeautifulSoup
</pre>
<pre style="background:#F7F7F7">class douyuSelenium(unittest.TestCase):</pre>
self.driver = webdriver.PhantomJS()
self.driver.get(
soup = BeautifulSoup(driver.page_source,
titles = soup.find_all(
nums = soup.find_all(
self.driver.find_element_by_class_name(
self.driver.quit()
<pre style="background:#F7F7F7">if __name__ ==
"main":
</pre>
unittest.main()
1.1.5 案例三:执行 JavaScript 语句
1.隐藏百度图片
<pre style="background:#F7F7F7">from selenium
import webdriver
</pre>
driver = webdriver.PhantomJS()
driver.get(
<pre style="background:#F7F7F7"># 给搜索输入框标红的javascript脚本</pre>
js =
<pre style="background:#F7F7F7"># 调用给搜索输入框标红js脚本</pre>
driver.execute_script(js)
<pre style="background:#F7F7F7">#查看页面快照</pre>
driver.save_screenshot(
<pre style="background:#F7F7F7">#js隐藏元素,将获取的图片元素隐藏</pre>
img = driver.find_element_by_xpath(
driver.execute_script(
<pre style="background:#F7F7F7"># 向下滚动到页面底部</pre>
driver.execute_script(
<pre style="background:#F7F7F7">#查看页面快照</pre>
driver.save_screenshot(
driver.quit()
2.模拟滚动条滚动到底部
<pre style="background:#F7F7F7">from selenium
import webdriver
</pre>
<pre style="background:
F7F7F7">import time
</pre>
driver = webdriver.PhantomJS()
driver.get(
<pre style="background:#F7F7F7"># 向下滚动10000像素</pre>
js =
<pre style="background:#F7F7F7">#js="var q=document.documentElement.scrollTop=10000"</pre>
time.sleep(
<pre style="background:#F7F7F7">#查看页面快照</pre>
driver.save_screenshot(
<pre style="background:#F7F7F7"># 执行JS语句</pre>
driver.execute_script(js)
time.sleep(
<pre style="background:#F7F7F7">#查看页面快照</pre>
driver.save_screenshot(
driver.quit()
1.1.6 机器视觉与Tesseract介绍
机器视觉
从 Google 的无人驾驶汽车到可以识别假钞的自动售卖机,机器视觉一直都是一个应用广 泛且具有深远的影响和雄伟的愿景的领域。
我们将重点介绍机器视觉的一个分支:文字识别,介绍如何用一些 Python库来识别和使用在线图片中的文字。
我们可以很轻松的阅读图片里的文字,但是机器阅读这些图片就会非常困难,利用这种人类用户可以正常读取但是大多数机器人都没法读取的图片,验证码 (CAPTCHA)就出现了。验证码读取的难易程度也大不相同,有些验证码比其他的更加难读。
将图像翻译成文字一般被称为 **光学文字识别**
(Optical Character Recognition, OCR)。可以实现OCR的底层库并不多,目前很多库都是使用共同的几个底层 OCR 库,或者是在上面 进行定制。
ORC库概述
在读取和处理图像、图像相关的机器学习以及创建图像等任务中,Python 一直都是非常出色的语言。虽然有很多库可以进行图像处理,但在这里我们只重点介绍: Tesseract
Tesseract
Tesseract 是一个 OCR 库,目前由 Google 赞助(Google 也是一家以 OCR 和机器学习技术闻名于世的公司)。Tesseract 是目前公认最优秀、最精确的开源 OCR 系统,除了极高的精确度,Tesseract 也具有很高的灵活性。它可以通过训练识别出任何字体,也可以识别出任何 Unicode 字符。
安装Tesseract
- Windows 系统
下载可执行安装文件:https://code.google.com/p/tesseract-ocr/downloads/list 安装。
- Ubuntu Linux系统
可以通过 apt-get 安装: $sudo apt-get tesseract-ocr
- Mac OS X系统
用 Homebrew可以很方便地安装: brew install tesseract
要使用 Tesseract 的功能,比如后面的示例中训练程序识别字母,要先在系统中设置一 个新的环境变量 $TESSDATA_PREFIX
,让 Tesseract 知道训练的数据文件存储在哪里,然后搞一份tessdata数据文件,放到Tesseract目录下。
在大多数 Linux 系统和 Mac OS X 系统上,你可以这么设置(假设Tesseract数据文件目录在/usr/local/share/下):
**$export TESSDATA_PREFIX=/usr/local/share/Tesseract**
在 Windows 系统上也类似,你可以通过下面这行命令设置环境变量:
**#setx TESSDATA_PREFIX C:\Program Files\Tesseract OCR\Tesseract**
安装pytesseract
Tesseract 是一个命令行工具,安装之后,要用 tesseract 命令在 Python 的外面运行,但我们可以通过 pip 安装支持 Python 版本的 Tesseract库:pytesseract
pip install pytesseract
1.1.7 处理一些格式规范的文字
处理给规范的文字
处理的大多数文字最好都是比较干净、格式规范的。格式规范的文字通常可以满足一些需求,通常格式规范的文字具有以下特点:
Ø 使用一个标准字体(不包含手写体、草书,或者十分“花哨的”字体)
Ø 即使被复印或拍照,字体还是很清晰,没有多余的痕迹或污点
Ø 排列整齐,没有歪歪斜斜的字
Ø 没有超出图片范围,也没有残缺不全,或紧紧贴在图片的边缘
文字的一些格式问题在图片预处理时可以进行解决。例如,可以把图片转换成灰度图,调整亮度和对比度,还可以根据需要进行裁剪和旋转(详情需要了解图像与信号处理)等。
格式规范文字的理想示例
<v:shape id="图片_x0020_42" o:spid="_x0000_i1033" type="#_x0000_t75" style="width:415.2pt;height:69.6pt;
visibility:visible;mso-wrap-style:square"><v:imagedata src="file:///C:/Users/10843/AppData/Local/Temp/msohtmlclip1/01/clip_image003.png" o:title=""></v:imagedata></v:shape>
通过下面的命令运行 Tesseract,读取文件并把结果写到一个文本文件中:
|
tesseract test.jpg text
cat text.txt
|
识别结果很准确,不过符号^
和*
分别被表示成了双引号和单引号。大体上可以让你很舒服地阅读。
通过Python代码实现
<pre style="background:#F7F7F7">import pytesseract
</pre>
<pre style="background:
F7F7F7">from PIL
import Image
</pre>
image = Image.open(
text = pytesseract.image_to_string(image)
<pre style="background:#F7F7F7">print(text)
</pre>
运行结果:
This is some text, written in Arial, that will be read by
Tesseract. Here are some symbols: !@#$%"&*()
对图片进行阈值过滤和降噪处理(了解即可)
很多时候我们在网上会看到这样的图片:
<v:shape id="图片_x0020_43" o:spid="_x0000_i1032" type="#_x0000_t75" style="width:415.2pt;height:69pt;visibility:visible;mso-wrap-style:square"><v:imagedata src="file:///C:/Users/10843/AppData/Local/Temp/msohtmlclip1/01/clip_image004.png" o:title=""></v:imagedata></v:shape>
tesseract 不能完整处理这个图片,主要是因为图片背景色是渐变的,最终结果是这样:
<v:shape id="图片_x0020_46" o:spid="_x0000_i1031" type="#_x0000_t75" style="width:414.6pt;height:72.6pt;visibility:visible;mso-wrap-style:square"><v:imagedata src="file:///C:/Users/10843/AppData/Local/Temp/msohtmlclip1/01/clip_image005.png" o:title=""></v:imagedata></v:shape>
随着背景色从左到右不断加深,文字变得越来越难以识别,Tesseract 识别出的 每一行的最后几个字符都是错的。
遇到这类问题,可以先用 Python 脚本对图片进行清理。利用 PIL 库,我们可以创建一个阈值过滤器来去掉渐变的背景色,只把文字留下来,从而让图片更加清晰,便于 Tesseract 读取:
from PIL import Image
import subprocess
def cleanFile(filePath, newFilePath):
image = Image.open(filePath)
对图片进行阈值过滤(低于143的置为黑色,否则为白色)
image = image.point(lambda x: 0 if x < 143 else 255)
重新保存图片
image.save(newFilePath)
调用系统的tesseract命令对图片进行OCR识别
subprocess.call(["tesseract", newFilePath, "output"])
打开文件读取结果
with open("output.txt", 'r') as f:
print(f.read())
if name == "main":
cleanFile("text2.png", "text2clean.png")
通过一个阈值对前面的“模糊”图片进行过滤的结果
<v:shape id="图片_x0020_47" o:spid="_x0000_i1030" type="#_x0000_t75" style="width:415.2pt;height:69pt;
visibility:visible;mso-wrap-style:square"><v:imagedata src="file:///C:/Users/10843/AppData/Local/Temp/msohtmlclip1/01/clip_image006.png" o:title=""></v:imagedata></v:shape>
除了一些标点符号不太清晰或丢失了,大部分文字都被读出来了。Tesseract 给出了最好的 结果:
<v:shape id="图片_x0020_48" o:spid="_x0000_i1029" type="#_x0000_t75" style="width:418.2pt;height:158.4pt;
visibility:visible;mso-wrap-style:square"><v:imagedata src="file:///C:/Users/10843/AppData/Local/Temp/msohtmlclip1/01/clip_image007.png" o:title=""></v:imagedata></v:shape>
从网站图片中抓取文字
用 Tesseract 读取硬盘里图片上的文字,可能不怎么令人兴奋,但当我们把它和网络爬虫组合使用时,就能成为一个强大的工具。
网站上的图片可能并不是故意把文字做得很花哨 (就像餐馆菜单的 JPG 图片上的艺术字),但它们上面的文字对网络爬虫来说就是隐藏起来 了,举个例子:
虽然亚马逊的 robots.txt 文件允许抓取网站的产品页面,但是图书的预览页通常不让网络机 器人采集。
图书的预览页是通过用户触发 Ajax 脚本进行加载的,预览图片隐藏在 div 节点 下面;其实,普通的访问者会觉得它们看起来更像是一个 Flash 动画,而不是一个图片文 件。当然,即使我们能获得图片,要把它们读成文字也没那么简单。
下面的程序就解决了这个问题:首先导航到托尔斯泰的《战争与和平》的大字号印刷版 1, 打开阅读器,收集图片的 URL 链接,然后下载图片,识别图片,最后打印每个图片的文 字。因为这个程序很复杂,利用了前面几章的多个程序片段,所以我增加了一些注释以让 每段代码的目的更加清晰:
<pre style="background:#F7F7F7">import time
</pre>
<pre style="background:#F7F7F7">from urllib.request
import urlretrieve
</pre>
<pre style="background:
F7F7F7">import subprocess
</pre>
<pre style="background:#F7F7F7">from selenium
import webdriver
</pre>
<pre style="background:
F7F7F7">#创建新的Selenium driver</pre>
driver = webdriver.PhantomJS()
<pre style="background:#F7F7F7"># 用Selenium试试Firefox浏览器:</pre>
<pre style="background:#F7F7F7"># driver = webdriver.Firefox()</pre>
driver.get(
<pre style="background:#F7F7F7"># 单击图书预览按钮 driver.find_element_by_id("sitbLogoImg").click() imageList = set()</pre>
<pre style="background:#F7F7F7"># 等待页面加载完成</pre>
time.sleep(
<pre style="background:#F7F7F7"># 当向右箭头可以点击时,开始翻页</pre>
<pre style="background:#F7F7F7">while"pointer"in driver.find_element_by_id(
"sitbReaderRightPageTurner").get_attribute(
"style"):
</pre>
driver.find_element_by_id(
time.sleep(
pages = driver.find_elements_by_xpath(
image = page.get_attribute(
imageList.add(image)
driver.quit()
<pre style="background:#F7F7F7"># 用Tesseract处理我们收集的图片URL链接 </pre>
<pre style="background:#F7F7F7">for image
in sorted(imageList):
</pre>
urlretrieve(image,
p = subprocess.Popen([
f = open(
p.wait() print(f.read())
和我们前面使用 Tesseract 读取的效果一样,这个程序也会完美地打印书中很多长长的段落,第六页的预览如下所示:
6
"A word of friendly advice, mon
cher. Be off as soon as you can,
that's all I have to tell you. Happy
he who has ears to hear. Good-by,
my dear fellow. Oh, by the by!" he
shouted through the doorway after
Pierre, "is it true that the countess
has fallen into the clutches of the
holy fathers of the Society of je-
sus?"
Pierre did not answer and left Ros-
topchin's room more sullen and an-
gry than he had ever before shown
himself.
但是当文字出现在彩色封面上时,结果就不那么完美了:
WEI' nrrd Peace
Len Nlkelayevldu Iolfluy
Readmg shmdd be ax
wlnvame asnossxble Wenfler
an mm m our cram: Llhvary
- Leo Tmsloy was a Russian rwovelwst
I and moval phflmopher med lur
A ms Ideas 01 nonviolenx reswslance m 5 We range 0, "and"
如果想把文字加工成普通人可以看懂的效果,还需要花很多时间去处理。
比如,通过给 Tesseract 提供大量已知的文字与图片映射集,经过训练 Tesseract 就可以“学会”识别同一种字体,而且可以达到极高的精确率和准确率,甚至可以忽略图片中文字的背景色和相对位置等问题。
1.1.8 案例:尝试对验证码进行机器识别处理
许多流行的内容管理系统即使加了验证码模块,其众所周知的注册页面也经常会遭到网络 机器人的垃圾注册。
那么,这些网络机器人究,竟是怎么做的呢?既然我们已经,可以成功地识别出保存在电脑上 的验证码了,那么如何才能实现一个全能的网络机器人呢?
大多数网站生成的验证码图片都具有以下属性。
它们是服务器端的程序动态生成的图片。验证码图片的 src 属性可能和普通图片不太一 样,比如 <img src="WebForm.aspx?id=8AP85CQKE9TJ">,但是可以和其他图片一样进行 下载和处理。
图片的答案存储在服务器端的数据库里。
很多验证码都有时间限制,如果你太长时间没解决就会失效。
常用的处理方法就是,首先把验证码图片下载到硬盘里,清理干净,然后用 Tesseract 处理 图片,最后返回符合网站要求的识别结果。
|
import requests
import time
import pytesseract
from PIL import Image
from bs4 import BeautifulSoup
def captcha(data):
with open('captcha.jpg','wb') as fp:
fp.write(data)
time.sleep(1)
image = Image.open("captcha.jpg")
text = pytesseract.image_to_string(image)
print "机器识别后的验证码为:" + text
command = input("请输入Y表示同意使用,按其他键自行重新输入:")
if (command == "Y" or command == "y"):
return text
else:
return input('输入验证码:')
def zhihuLogin(username,password):
构建一个保存Cookie值的session对象
sessiona = requests.Session()
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0'}
先获取页面信息,找到需要POST的数据(并且已记录当前页面的Cookie)
html = sessiona.get('https://www.zhihu.com/#signin', headers=headers).content
找到 name 属性值为 _xsrf 的input标签,取出value里的值
_xsrf = BeautifulSoup(html ,'lxml').find('input', attrs={'name':'_xsrf'}).get('value')
取出验证码,r后面的值是Unix时间戳,time.time()
captcha_url = 'https://www.zhihu.com/captcha.gif?r=%d&type=login' % (time.time() * 1000)
response = sessiona.get(captcha_url, headers = headers)
data = {
"_xsrf":_xsrf,
"email":username,
"password":password,
"remember_me":True,
"captcha": captcha(response.content)
}
response = sessiona.post('https://www.zhihu.com/login/email', data = data, headers=headers)
print(response.text)
response = sessiona.get('https://www.zhihu.com/people/maozhaojun/activities', headers=headers)
print(response.text)
if name == "main":
username = raw_input("username")
password = raw_input("password")
zhihuLogin('xxxx@qq.com','ALAxxxxIME')
|
尝试处理中文字符
如果手头上有中文的训练数据,也可以尝试对中文进行识别。
命令:tesseract --list-langs
可以查看当前支持的语言,chi_sim
表示支持简体中文。
那么在使用时候,可以指定某个语言来进行识别,如:
tesseract -l chi_sim paixu.png paixu
<v:shape id="图片_x0020_45" o:spid="_x0000_i1028" type="#_x0000_t75" style="width:415.2pt;height:121.8pt;
visibility:visible;mso-wrap-style:square"><v:imagedata src="file:///C:/Users/10843/AppData/Local/Temp/msohtmlclip1/01/clip_image008.jpg" o:title=""></v:imagedata></v:shape>
表现在程序里,则可以这么写:
|
from PIL import Image
import subprocess
def cleanFile(filePath):
image = Image.open(filePath)
调用系统的tesseract命令, 对图片进行OCR中文识别
subprocess.call(["tesseract", "-l", "chi_sim", filePath, "paixu"])
打开文件读取结果
with open("paixu.txt", 'r',encoding='utf-8') as f:
print(f.read())
if name == "main":
cleanFile("paixu.png")
|
结果如下:
<v:shape id="图片_x0020_49" o:spid="_x0000_i1027" type="#_x0000_t75" style="width:417.6pt;height:309.6pt;
visibility:visible;mso-wrap-style:square"><v:imagedata src="file:///C:/Users/10843/AppData/Local/Temp/msohtmlclip1/01/clip_image009.png" o:title=""></v:imagedata></v:shape>
1.1.9 参考阅读:训练Tesseract
大多数其他的验证码都是比较简单的。例如,流行的 PHP 内容管理系统 Drupal 有一个著名的验证码模块 https://www.drupal.org/project/captcha,可以生成不同难度的验证码。
举个例子:
<v:shape id="图片_x0020_50" o:spid="_x0000_i1026" type="#_x0000_t75" style="width:415.2pt;height:189.6pt;
visibility:visible;mso-wrap-style:square"><v:imagedata src="file:///C:/Users/10843/AppData/Local/Temp/msohtmlclip1/01/clip_image010.png" o:title=""></v:imagedata></v:shape>
那么与其他验证码相比,为什么这个验证码更容易被人类和机器读懂呢?
· 字母没有相互叠加在一起,在水平方向上也没有彼此交叉。也就是说,可以在每一个字 母外面画一个方框,而不会重叠在一起。
· 图片没有背景色、线条或其他对 OCR 程序产生干扰的噪点。
· 虽然不能因一个图片下定论,但是这个验证码用的字体种类很少,而且用的是 sans-serif 字体(像“4”和“M”)和一种手写形式的字体(像“m”“C”和“3”)。
· 白色背景色与深色字母之间的对比度很高。
这个验证码只做了一点点改变,就让 OCR 程序很难识别。
· 字母和数据都使用了,这会增加待搜索字符的数量。
· 字母随机的倾斜程度会迷惑 OCR 软件,但是人类还是很容易识别的。
· 那个比较陌生的手写字体很有挑战性,在“C”和“3”里面还有额外的线条。
· 另外这个非常小的小写“m”,计算机需要进行额外的训练才能识别。
用下面的代码运行 Tesseract 识别图片:
tesseract captchaExample.png output
我们得到的结果 output.txt 是: 4N\``,,,``C<3
1.1.10 创建样本库训练Tesseract
要训练 Tesseract 识别一种文字,无论是晦涩难懂的字体还是验证码,你都需要向 Tesseract 提供每个字符不同形式的样本。
首先要收集大量的验证码样本,样本的数量和复杂程度,会决定训练的效果。第二步是准确地告诉 Tesseract 一张图片中的每个字符是什么,以及每个字符的具体位置。
这里需要创建一些矩形定位文件(box file),一个验证码图片生成一个矩形定位文件,也可以通过jTessBoxEditor软件来修改矩形的定位。
一个图片的矩形定位文件如下所示:
4 15 26 33 55 0
M 38 13 67 45 0
m 79 15 101 26 0
C 111 33 136 60 0
3 147 17 176 45 0
第一列符号是图片中的每个字符,后面的 4 个数字分别是包围这个字符的最小矩形的坐标 (图片左下角是原点 (0,0),4 个数字分别对应每个字符的左下角 x 坐标、左下角 y 坐标、右上角 x 坐标和右上角 y 坐标),最后一个数字“0”表示图片样本的编号。
矩形定位文件必须保存在一个 .box 后缀的文本文件中,(例如 4MmC3.box)。
博客园的一篇不错的训练教程:http://www.cnblogs.com/mjorcen/p/3800739.html?utm_source=tuicool&utm_medium=referral
前面的内容只是对 Tesseract 库的字体训练和识别能力的一个简略概述。如果你对 Tesseract 的其他训练方法感兴趣,甚至打算建立自己的验证码训练文件库,推荐阅读 Tesseract 官方文档:https://github.com/tesseract-ocr/tesseract/wiki,加油!
1.2 Scrapy 框架
Scrapy是用纯Python实现一个为了爬取网站数据、提取结构性数据而编写的应用框架,用途非常广泛。
框架的力量,用户只需要定制开发几个模块就可以轻松的实现一个爬虫,用来抓取网页内容以及各种图片,非常之方便。
Scrapy 使用了 Twisted'twɪstɪd异步网络框架来处理网络通讯,可以加快我们的下载速度,不用自己去实现异步框架,并且包含了各种中间件接口,可以灵活的完成各种需求。
Scrapy架构图(绿线是数据流向):
<v:shape id="图片_x0020_51" o:spid="_x0000_i1025" type="#_x0000_t75" style="width:415.8pt;height:292.8pt;visibility:visible;mso-wrap-style:square"><v:imagedata src="file:///C:/Users/10843/AppData/Local/Temp/msohtmlclip1/01/clip_image011.png" o:title=""></v:imagedata></v:shape>
Scrapy Engine(引擎): 负责Spider、ItemPipeline、Downloader、Scheduler中间的通讯,信号、数据传递等。
Scheduler(调度器): 它负责接受引擎发送过来的Request请求,并按照一定的方式进行整理排列,入队,当引擎需要时,交还给引擎。
Downloader(下载器):负责下载Scrapy Engine(引擎)发送的所有Requests请求,并将其获取到的Responses交还给Scrapy Engine(引擎),由引擎交给Spider来处理,
Spider(爬虫):它负责处理所有Responses,从中分析提取数据,获取Item字段需要的数据,并将需要跟进的URL提交给引擎,再次进入Scheduler(调度器),
Item Pipeline(管道):它负责处理Spider中获取到的Item,并进行进行后期处理(详细分析、过滤、存储等)的地方.
Downloader Middlewares(下载中间件):你可以当作是一个可以自定义扩展下载功能的组件。
Spider Middlewares(Spider中间件):你可以理解为是一个可以自定扩展和操作引擎和Spider中间通信的功能组件(比如进入Spider的Responses;和从Spider出去的Requests)