day068 多任务爬虫&Selenium+PhantomJS&Tesseract-OCR

对于具有大量数据的爬虫任务,单进程/线程就会显得捉襟见肘,爬取速度会比较慢,如果需要加快速度,就需要选择多线程/协程 进行处理;如果反爬虫中有对js代码进行加密的时候,一般的爬虫手段都会失效,那么解决的办法有一种就是,直接调用Selenium测试框架控制浏览器进行代码自动发送请求,对返回的真实页面的数据进行解析;在爬虫过程中,如果有验证码图片的时候,对于一般的黑白清晰字码,可以使用tesseract模块进行识别。

多任务进行爬虫

  • 多任务爬虫的目标就是更快的将数据爬下来,对比单线程爬取和多任务(多线程,协程爬取的时间)

示例对比:爬取豆瓣电影top250

单线程爬虫

代码:

# -*- coding:utf-8 -*-
import requests
import json, time
from lxml import etree


class Spider_Douban(object):
    def __init__(self):
        self.base_url = 'https://movie.douban.com/top250?filter=&start='
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36'}
        self.data_list = []

    # 发送请求
    def send_request(self, url):
        data = requests.get(url, headers=self.headers).content
        return data

    # 数据解析
    def parse_data(self, data):
        html = etree.HTML(data)
        data_list = html.xpath('//*[@id="content"]/div/div[1]/ol/li/div/div[2]/div[1]/a/span[1]/text()')
        for data in data_list:
            self.data_list.append(data)

    # 保存数据到本地
    def save_data(self):
        data_json = json.dumps(self.data_list)
        with open('03_spider_douban.json', 'w') as f:
            f.write(data_json)

    # 主逻辑
    def main(self):
        import time
        start_time = time.time()
        for page in range(0, 225 + 1, 25):
            url = self.base_url + str(page)
            # 发送数据
            data = self.send_request(url)
            # 数据解析
            self.parse_data(data)
            print '正在爬第--%d--页' % ((page / 25) + 1)
        # 保存数据
        self.save_data()
        end_time = time.time()
        time = end_time - start_time
        print '单线程所需时间为:%s' % time
        # 单线程所需时间为:2.55781912804

if __name__ == '__main__':
    spider_douban = Spider_Douban()
    spider_douban.main()

多线程

步骤

  1. 导入模块:import threading
  2. 将任务加入到异步线程中:threading.Thread(target=self.change_value)
  3. 开始线程:t1.start()
    注意点:将子线程加入到主线程中:t1.join()

代码

# -*- coding:utf-8 -*-
import threading
import time


class Test_Threads(object):

    def change_value(self):
        global a
        a = 200
        time.sleep(5)
        print '这是修改a的子线程1,a = %d'%a

    def read_value(self):
        print '这是读取a的子线程2,a = %d'%a

    def run(self,):
        t1 = threading.Thread(target=self.change_value)
        t1.start()
        t1.join()
        threading.Thread(target=self.read_value).start()

        print '这是主线程'




if __name__ == '__main__':
    a = 100
    test_thread= Test_Threads()
    test_thread.run()

线程池thread_pool的使用

  • 使用线程池可以固定线程的个数,在加快任务处理速度的同时,避免过度消耗系统资源

步骤

  1. 导入: from multiprocessing.dummy import Pool
  2. 创建线程池:thread_pool = Pool(len(url_list))
  3. 给线程池添加任务:thread_pool.map(self.send_request, url_list)
  4. 关闭线程池任务的添加:thread_pool.close()
  5. 将线程池中的任务队列加入到主线程中,等待所有队列任务执行结束后,再继续执行下面代码:thread_pool.join()

代码:

# -*- coding:utf-8 -*-
import requests
import json
from multiprocessing.dummy import Pool
from lxml import etree


class Spider_Douban(object):
    def __init__(self):
        self.base_url = 'https://movie.douban.com/top250?filter=&start='
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36'
        }
        self.data_list = []

    # 发送请求,解析数据
    def send_request(self, url):
        data = requests.get(url, headers=self.headers).content
        self.parse_data(data)

    def parse_data(self, data):
        html = etree.HTML(data)
        data_list = html.xpath('//*[@id="content"]/div/div[1]/ol/li/div/div[2]/div[1]/a/span[1]/text()')
        for data in data_list:
            self.data_list.append(data)

    # 保存数据到本地
    def save_data(self):
        data_json = json.dumps(self.data_list)
        with open('03_spider_douban.json', 'w') as f:
            f.write(data_json)

    # 主逻辑
    def main(self):
        import time
        start_time = time.time()
        url_list = []
        for page in range(0, 225 + 1, 25):
            url = self.base_url + str(page)
            url_list.append(url)
        # 使用线程池处理
        # 1.创建线程池
        thread_pool = Pool(len(url_list))
        # 2.给线程池添加任务
        thread_pool.map(self.send_request, url_list)
        # 3.关闭线程池
        thread_pool.close()
        # 4.将所有线程join到主线程中
        thread_pool.join()

        # 保存数据
        self.save_data()
        end_time = time.time()
        time = end_time - start_time
        print '线程池所需时间为:%s' % time
        # 线程池所需时间为:2.41724801064


if __name__ == '__main__':
    spider_douban = Spider_Douban()
    spider_douban.main()

协程

步骤

  1. 导入模块:import gevent
  2. 导入设置自动跳转的monkey模块并且使用:from gevent import monkey; monkey.patch_all()
  3. 添加任务到协程中:gevent = gevent.spawn(self.send_request, url)
  4. 让主线程等待所有协程执行完毕(join):gevent.joinall(gevent_list)

代码

# !/usr/bin/env python
# _*_ coding:utf-8 _*_


import requests
from lxml import etree
import time
import gevent
from gevent import monkey

monkey.patch_all()


class Douban_Spider(object):
    def __init__(self):
        self.base_url = 'https://movie.douban.com/top250?filter=&start='
        self.headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko"}
        self.count = 0

    # 请求数据
    def send_request(self, url):
        time.sleep(1)
        try:
            data = requests.get(url, headers=self.headers).content
            self.analysis_data(data)
        except Exception, err:
            print err

    # 解析数据
    def analysis_data(self, data):

        # 1.转换类型
        html_data = etree.HTML(data)

        # 2.解析 -->list
        data_list = html_data.xpath('//*[@id="content"]/div/div[1]/ol/li/div/div[2]/div[1]/a/span[1]/text()')

        for name in data_list:
            print name
            self.count += 1

    # 调度的方法
    def start_work(self):

        # 开始时间
        start_time = time.time()

        gevent_list = []
        for page in range(0, 225 + 1, 25):
            url = self.base_url + str(page)
            print url
            # 1.创建协程
            gevent = gevent.spawn(self.send_request, url)

            gevent_list.append(gevent)

        # 2. 让主线程等待, 协程执行完毕
        gevent.joinall(gevent_list)

        end_time = time.time()

        print end_time - start_time
        print self.count


if __name__ == '__main__':
    tool = Douban_Spider()
    tool.start_work()

Selenium + PhantomJS

  • 对于那些使用了ajax请求和DHTML技术的网页,传统的直接请求url方式就行不通了,因为那样只能请求到没有被执行js代码前的页面,即不完整的页面,解决这个问题的办法,有两个:

    1. 直接从js代码里采集内容,这种方式,费时费力,不好搞
    2. 使用第三方的库,采集执行过js代码之后的,直接在浏览器里看到的页面内容
  • Selenium就是这样一个库,它是一个自动化测试工具,也可以用于爬虫开发,它可以自动化控制浏览器(常规浏览器,谷歌,火狐,甚至无界面浏览器,PhantomJS),利用selenium控制浏览器请求网页,返回响应,然后抓取响应的网页内容进行解析,就能解决,使用了ajax,DHTML技术,不能直接请求url提取信息的问题。

  • Phantoms是一种无界面的浏览器,因为不需要加载界面信息,所以请求加载页面,返回结果速度比有界面的浏览器要快速的多。

使用步骤:

  1. 导入模块:from selenium import webdriver
  2. 创建浏览器对象:driver = webdriver.PhantomJS() # 这里使用的是无界面的PhantomJS浏览器
  3. 使用浏览器对象driver进行像正常用户一样的操作:
    1. 请求数据:driver.get(url)
    2. 获取页面数据:driver.page_source
    3. 获取页面按钮对象:button = driver.find_element_by_xpath()(也可以使用by_class, by_id等方式)
    4. 点击按钮 :button.click()
    5. 在输入框输入内容:element.send_keys(u’…’)(注意输入的内容要是Unicode的编码,所以要加上u”)
    6. 获取当前页面:driver.window_handles
    7. 切换页面:driver.switch_to_window(driver.window_handles[1])
    8. 获取cookies: driver.get_cookies()
    9. 获取当前页面的url:driver.current_url
    10. 保存快照:driver.save_screenshot(‘文件名’)
    11. 关闭浏览器:driver.quit()
    12. 关闭页面:driver.close()

完整使用步骤代码:

# -*- coding:utf-8 -*-
from selenium import webdriver
import time


def base_use_selenium():
    url = 'https://www.so.com/'

    # 1.创建浏览器对象
    driver = webdriver.PhantomJS()

    # 2.请求数据
    driver.get(url)

    # 4.获取数据
    data = driver.page_source # 格式为unicode,如需保存需要:data.encode('utf-8')

    # 5.点击新闻按钮
    # 获取按钮对象
    button = driver.find_element_by_xpath('//*[@id="bd_tabnav"]/nav/a[2]')
    # 点击按钮
    button.click()

    # 6.在输入框中输入内容
    # 获取输入框对象
    element =  driver.find_element_by_id('haosou-input')
    # 输入内容
    element.send_keys(u'中兴')

    # 7.点击搜索按钮(放大镜)进行
    driver.find_element_by_xpath('//*[@id="search-form"]//div/button').click()

    # 8.获取当前的页面
    print driver.window_handles

    # 9.切换页面
    driver.switch_to_window(driver.window_handles[1])

    # 10.获取cookies
    cookies = driver.get_cookies()

    # 11.获取当前页面的url
    current_url = driver.current_url

    # 3.保存快照
    driver.save_screenshot('so.png')

    # 12.关闭浏览器
    driver.quit()


if __name__ == '__main__':
    base_use_selenium()

示例:使用selenium-PhantomJS登录到豆瓣首页

代码:

 -*- coding:utf-8 -*-
from selenium import webdriver
import time


def login_douban():
    # 1.登录的网址
    url = 'http://www.douban.com/accounts/login?source=movie'

    # 2.创建浏览器对象
    driver = webdriver.PhantomJS()

    # 3.请求url
    driver.get(url)

    # 4.输入用户名
    driver.find_element_by_id('email').send_keys(u'[email protected]')

    # 5.输入密码
    driver.find_element_by_id('password').send_keys(u'ALARMCHIME')

    # 6.点击登录按钮,看是否出现验证码
    driver.find_element_by_name('login').click()
    time.sleep(2)  # 等待页面加载完成

    # 保存快照,查看验证码,手动输入,此处若使用第三方SDK(某云,极验)可以将验证码图片当参数传给第三方接口
    # 第三方平台处理好后,返回结果,直接输入结果即可,这里不调用,所以要手动输入
    driver.save_screenshot('veri_code.png')
    code = raw_input('请输入验证码')

    # 7.输入验证码
    driver.find_element_by_id('captcha_field').send_keys(code)

    # 8.点击登录按钮
    driver.find_element_by_name('login').click()

    driver.save_screenshot('douban.png')


if __name__ == '__main__':
    login_douban()

Tesseract-OCR

  • 在爬虫过程中,难免会遇到验证码卡住爬虫进程的情况。在这种情况下怎么处理呢?
    1. 直接使用第三方平台的验证码识别sdk,如极验,某云,要钱
    2. 对于简单的验证码,可以使用成熟的工具包,如Tesseract

使用步骤;

  1. 先把验证码图片下载下来,清理干净
  2. 然后使用Tesseract处理图片,处理完的结果使用selenium创建的driver,使用send_keys()方法输入,获取确认按钮,然后button.click()进入,主页

示例:识别豆瓣验证码

# -*- coding:utf-8 -*-
from selenium import webdriver
import time


def login_douban():
    # 1.登录的网址
    url = 'http://www.douban.com/accounts/login?source=movie'

    # 2.创建浏览器对象
    driver = webdriver.PhantomJS()

    # 3.请求url
    driver.get(url)

    # 4.输入用户名
    driver.find_element_by_id('email').send_keys(u'[email protected]')

    # 5.输入密码
    driver.find_element_by_id('password').send_keys(u'ALARMCHIME')

    # 6.点击登录按钮,看是否出现验证码
    driver.find_element_by_name('login').click()
    time.sleep(2)  # 等待页面加载完成

    # 保存快照,查看验证码,手动输入,此处若使用第三方SDK(某云,极验)可以将验证码图片当参数传给第三方接口
    # 第三方平台处理好后,返回结果,直接输入结果即可,这里不调用,所以要手动输入
    driver.save_screenshot('veri_code.png')
    code = raw_input('请输入验证码')

    # 7.输入验证码
    driver.find_element_by_id('captcha_field').send_keys(code)

    # 8.点击登录按钮
    driver.find_element_by_name('login').click()

    driver.save_screenshot('douban.png')


if __name__ == '__main__':
    login_douban()

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章