关键字: Python 爬虫 PhantomJS MongoDB Webdriver 模拟登陆 Fiddler
背景:想着搞一点公司的经营信息,开发的差不多了,数据也基本抓取完成了,但由于数据不是很准确,后来就没用了。这算是一个PhantomJS 的基础入门。这里对无头浏览器和Webdriver不做过多解释,如有需要请绕道 Google 自行搜索。
简单介绍一下这个网站: http://finance.chinascope.com 网站主要是面向股票分析人员的, 里面包含了上市公司的各种各样的数据,也有一些工具可以对数据进行分析。我们要采集的数据主要是公司的一些基本信息,和一些年报里的指标数据。为什么要用 PhantomJS,这里是有一定原因, 下面会讲到。
<h2>登陆</h2>
在写文章的时候,爬虫的登陆模块已经被重构了,因为网站更新了,原来直接 post 密码的方式不好使了,做了加密,一步步的来看。这部分重点讲, 解决了登陆问题,其余的都不是问题了。
首先,用 Fiddler 抓包看登陆请求:
很明显密码是做了加密的。之前是明文上传的,虽然也用的是 https。这里说一下 fiddler, 这货默认是不解https包的,不过if you want, it really can. 全平台的 Charles 好像是不能解https 的包。
遇到这种情况通常情况下,我一般会分析js 代码,找到加密的算法,看了看还是算了,涉及到几个 js 文件, 先做了 RSA 加密,又做了个 BASE64。可以把加密相关的 js 文件下载下来,可以借助例如pyv8 SpiderMonkey 抑或是 PhantomJS,因为懒,所以,我不去处理了,直接让 PhantomJS 去处理登陆的过程吧。登陆成功后,再把header 完完全全的复制一份到Python 的进程里。
先初始化 PhantomJS,代码如下:
def init_phantomjs(self):
try:
self.driver.delete_all_cookies()
self.driver.close()
self.driver.quit()
os.system("ps aux|grep phantomjs | awk '{print $2}'|xargs kill -9")
except Exception as e:
logging.error('close driver error '+str(e))
finally:
logging.info('init driver')
self.driver = webdriver.PhantomJS(executable_path=self.phantomjs_path)
self.driver.set_page_load_timeout(5*60)
PhantomJS 有比较多的坑的,最好在初始化的时候把所有的东西都清理干净。这货本来执行的比较慢,如果你发现不能及时的获取结果,要么用Selenium 的方法去等待元素的出现,或者有时你不得不sleep(休息,休息一会 😂 ,暴露年龄了)。
初始化完成,开始登陆了:
def login(self):
while True:
self.init_phantomjs()
try:
logging.info('phantomjs loging %s' % self.driver.current_url)
self.driver.get("about:blank")
self.driver.get(self.login_page)
#这里是做了明确的等待的,就是等待它出现
WebDriverWait(self.driver, 60).until(EC.presence_of_element_located((By.TAG_NAME, "input")))
self.driver.find_element_by_name("username").send_keys(self.login_user['name'])
self.driver.find_element_by_id("passwd").send_keys(self.login_user['pwd'])
self.driver.find_element_by_css_selector(".js-login").click()
logging.info('phantomjs login success ')
except Exception as e:
logging.error('login error : %s' % e)
logging.info('current url %s ' % (self.driver.current_url)
# 如果登陆失败,一直重试,
continue
#这里的等待是为了等待 PhantomJS的请求完成,并处理 cookie。不然,无法获取 cookie
time.sleep(5)
cookies = self.driver.get_cookies()
cookies.reverse()
Cookie = ''.join(map(lambda x:x['name']+"="+x['value']+";", cookies))
self.query_session.headers.update({'Cookie': Cookie})
return
注释里写了一些说明,还有些地方需要说明一下。
等待元素出现和提交登陆请求: 这些可以单独写到 js 文件里,就不需要在 python 的代码里添加页面的操作了。当然为了灵活,还是需要一些操作用 python 来实现的。
循环重试: 这里可以写成装饰器,根据raise 不同的Exception 分别 log,重构的时候加上。可以省去一大部分代码,不过有些地面还是需要用 while True 的方式去循环。
等待:很明显我用了 sleep 5s 来等待浏览器的完成。当初我在开发的时候,这坑大了去了。只要在调试的状态下一准能获得 cookie( 在这个地方下了断点, pdb.set_trace phantomjs 提供任务完成的时间), 一正常运行,就歇菜,获取的 cookie 还是没登陆之前的。这就蛋疼了,纠结了好久,知道它慢,不得已加了个 sleep,就成了。 当你也遇到这问题的时候,不防也 sleep 一下。
Cookie 的处理:获取 cookie 后 reverse 是为了构造同phantomjs一样的请求,字段的顺序一致。phantomjs 不支持获取 header,所以就只能自己获取 cookie 进行构造了。这里要注意,必须是一模一样的,不然是不行的。开发的时候,遇到了这个问题,感觉构造的字段都一样了,但死活不能访问,后来在把两个请求的 header 复制出来意义比较,UserAgent 不一样,少了PhantomJS/2.1.1。最后把 cookie 更新到requets的请求 session 里, 登录的过程基本就结束了。
<h2>爬取</h2>
爬去的过程就比较简单了。我这里使用了 pyquery,可以像 jquery 一样,随意的操作元素。这里不细说,最后把采集的数据,写入数据库。有些地方还使用了线程,独立的任务,独立运行。
<h2>后续</h2>
爬虫总体来说比较简单,也比较复杂,因为网页里有太多的不确定性,只能在一开始的时候先开发一个原型,在运行的过程中,不停的挖坑埋坑。还有一些去重和并发的问题,可以自己实现,不过推荐使用框架,比如 Scrapy 和 PySpider。Scrapy 在并发上是很有优势的,基于异步框架 Twisted(实在是有点老了,可以考虑换成相对比较新的 Gevent, Tornado, aiohttp 等, 其实Requests已经可以被 aiohttp 替换了),效率还是很不错的,其作者也已经开发商业应用平台了。PySpider 相对而言,使用的技术比较新,它有三个 fetcher(phantomjs, tornado, requests),分别满足不同的需求,默认使用 tornado,应该是考虑到异步的优势吧。它还有一个可视化的界面,这是它最大的特点,编辑和调试都可以在网页上进行,还支持 webdav 可以使用vim编辑器,我还没弄明白如何配置。
附上 github 地址: https://github.com/nicozhang/AlittleSpider