title: scrapy进阶开发date: 2018-06-13 16:08:15
tags: spider

Selenium

爬取动态网页

官方文档

需要先安装selenium:

  1. $ pip install selenium

需要注意的是selenium是模仿浏览器行为,需要下载driver来搭配。

Chrome: https://sites.google.com/a/chromium.org/chromedriver/downloads
Edge: https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/
Firefox: https://github.com/mozilla/geckodriver/releases
Safari: https://webkit.org/blog/6900/webdriver-support-in-safari-10/

在tools中新建脚本文件selenium_spider

from selenium import webdriver

browser = webdriver.Chrome(executable_path="E:\download\selenium\chromedriver.exe")

browser.get("https://guang.taobao.com/detail/index.htm?spm=a21bo.2017.2001.3.5af911d9RmltPj&uid=2758542224&sid=8094212425&scm=1007.15939.89001.100200300000000&pvid=40c54281-8e66-4601-84c9-6c4c03d60714&itemid=35109052504")

print(browser.page_source)

只需简单几行即可获得js动态加载后的页面内容。

之后只需要再通过Selector来获取具体内容。当然,selenium也有一些解析的方法,可以做一些特定的操作,但是提取内容还是Selector更好。

t_selector = Selector(text=browser.page_source)
print(t_selector.css(".testsetest h1::text").extract_first())

模拟登陆

知乎

使用selenium模拟登陆知乎:

browser.get("https://www.zhihu.com/signup?next=%2F")

browser.find_element_by_css_selector(".SignContainer-switch span").click()

browser.find_element_by_css_selector(".SignFlow-accountInputContainer div input[name='username']").send_keys("xxxxxxxxxxxxx")

browser.find_element_by_css_selector(".SignFlow-password div div input[name='password']").send_keys("xxxxxxxxxxxxx")

browser.find_element_by_css_selector(".SignFlow-submitButton").click()

send_keys():模拟输入

click():模拟点击

微博

使用selenium模拟登陆微博:

browser.maximize_window()
browser.get("https://www.weibo.com/")
time.sleep(15)
browser.find_element_by_css_selector("#loginname").send_keys("xxxxxxxxxxxxx")
browser.find_element_by_css_selector(".info_list.password input[node-type='password']").send_keys("xxxxxxxxxxxxxxxxxxxxxxx")
browser.find_element_by_css_selector(".info_list.login_btn a[node-type='submitBtn']").click()

需要注意其中的sleep和maxmize_window()

sleep:由于微博有跳转加载时间,需要停顿一段时间再进行输入操作。

maxmize_window():因为微博兼容性的原因,若半屏打开会导致登陆部分不在页面中加载,只有全屏才能加载。

模拟下拉页面

browser.get("https://www.oschina.net/blog")
time.sleep(5)
for i in range(10):
    browser.execute_script("window.scrollTo(0, document.body.scrollHeight); var lenOfPage=document.body.scrollHeight; return lenOfPage")
    time.sleep(3)

在python中可以直接写JavaScript代码,用browser的execute_script方法。

不加载图片

加载图片耗费大量的运行时间,设置不加载图片可以使我们的爬取更为迅速。

对chromedriver设置对应属性可以通过webdriver.ChromeOptions()来实现。

chrome_opt = webdriver.ChromeOptions()
prefs = {"profile.managed_default_content_settings.images": 2}
chrome_opt.add_experimental_option("prefs", prefs)
browser = webdriver.Chrome(executable_path="E:\download\selenium\chromedriver.exe", chrome_options=chrome_opt)
browser.get("https://www.taobao.com")

无界面浏览器phantomjs

下载

对于linux系统环境下,需要使用无界面浏览器。对于windows一般依旧采取chrome,因为性能更优。

弊端:多进程情况下phantomjs性能下降会很严重。

browser = webdriver.PhantomJS(executable_path="E:\download\phantomjs-2.1.1-windows\\bin\phantomjs.exe")
browser.get("https://chaoshi.detail.tmall.com/item.htm?spm=a3204.11641622.4538157102.2.6e237e44lGawqi&pos=2&acm=lb-zebra-355535-4093695.1003.1.3594048&id=41285559396&scm=1003.1.lb-zebra-355535-4093695.FF-hyhsfZA-725677994_B-370100_C-HB_D-41285559396_E-D_G-114.9-FF_41285559396_3594048")
print(browser.page_source)
# 关闭
browser.quit()

将selenium集成进scrapy

可以通过middlewares的方式将selenium集成到scrapy中:

from selenium import webdriver
from scrapy.http import HtmlResponse

class JSPageMiddleware(object):
    # 通过chrome请求动态网页
    def process_request(self, request, spider):
        if spider.name == "jobbole":
            spider.browser.get(request.url)
            import time
            time.sleep(3)
            print("访问:{0}".format(request.url))

            return HtmlResponse(url=spider.browser.current_url, body=spider.browser.page_source, encoding="utf-8", request=request)

其中,开启chromedriver要放置在对应的spider(jobbole.py)中:

from selenium import webdriver
from scrapy.xlib.pydispatch import dispatcher
from scrapy import signals

def __init__(self):
    self.browser = webdriver.Chrome(executable_path="E:\download\selenium\chromedriver.exe")
    super(JobboleSpider, self).__init__()
    dispatcher.connect(self.spider_closed, signals.spider_closed)

并且,要设置当spider结束运行时关闭chromedriver:

def spider_closed(self, spider):
    # 当爬虫退出的时候关闭chrome
    print("spider closed")
    self.browser.quit()

最终,要记得在setting中开启DOWNLOADER_MIDDLEWARES:

DOWNLOADER_MIDDLEWARES = {
    'ArticleSpider.middlewares.JSPageMiddleware': 1
}

其他动态网页获取介绍

pyVirtualDisplay

运行在Linux系统中,可以实现无界面爬取,但效果不如chrome稳定。

from pyvirtualdisplay import Display
display = Display(visible=0, size=(800, 600))
display.start()

browser = webdriver.Chrome()
browser.get()
......

scrapy-splash

它自己运行一个servers,通过http请求的方式以js来执行。

运行在servers,支持分布式。

selenium grid

请求一个服务,通过api的方式发送请求。

splinter

操控浏览器的解决方案,和selenium很相像,是纯python代码。

scrapy暂停与重启

可以在spider运行暂停时进行记录,以保证下次开启spider从暂停位置继续。

需要在命令行中进行,因为其终止信号为Ctrl + c,而pycharm中终止并无法发出这一信号。

Windows:暂停信号:Ctrl + c Linux:kill -f main.py

在项目目录下进入虚拟环境后执行下面的语句:

$ scrapy crawl jobbole -s JOBDIR=job_info/001

需要在项目下新建存放信息的文件夹job_info

Telnet

文档

可以实现对爬虫的监听:

先在windows功能中开启telent,然后在命令行中输入:

$ telent localhost 6023

通过est()命令来查看当前spider状态。

并且可以打印spider的诸多信息,如setting等。

数据收集

scrapy自带数据收集器,文档

以jobbole为例:

收集伯乐在线所有404的url以及404页面数

handle_httpstatus_list = [404]
def __init__(self):
    self.fail_urls = []

在parse中可以进行判断:

if response.status == 404:
    self.fail_urls.append(response.url)
    self.crawler.stats.inc_value("failed_url")

信号

文档

Scrapy广泛使用信号来通知特定事件发生的时间。您可以捕获Scrapy项目中的一些信号(例如使用扩展)来执行其他任务或扩展Scrapy以添加不提供的功能。

以之前的收集404的url为例:

handle_httpstatus_list = [404]

def __init__(self):
    self.fail_urls = []
    dispatcher.connect(self.handle_spider_closed, signals.spider_closed)

def handle_spider_closed(self, spider,reason):
    self.crawler.ststs.set_value("failed_urls", ",".join(self.fail_urls))
    pass

可以在spider关闭(closed)之后输出404的url信息。