文档章节

Windows下Scrapy使用spynner爬取ajax分页数据并保存到MySQL数据库

L
 Listen_ing
发布于 2017/01/06 18:55
字数 1877
阅读 387
收藏 2

软件环境

OS系统:win7 64bit 专业版

IDE:pycharm 社区版

Python:2.7.12

spynner:2.19

实验步骤:

新建爬虫工程

Microsoft Windows [版本 6.1.7601]
版权所有 (c) 2009 Microsoft Corporation。保留所有权利。

C:\Windows\System32>d:

D:\>cd Anaconda2-project

D:\Anaconda2-project>scrapy startproject StockBaseInfoSpider

创建成功后,用pycharm打开这个工程,类似这样的

编辑代码

编辑items.py,如下

# -*- coding: utf-8 -*-

# Define here the models for your scraped items
#
# See documentation in:
# http://doc.scrapy.org/en/latest/topics/items.html

from scrapy import Item, Field

class StockbaseinfospiderItem(Item):
    # 公司代码
    compCode = Field()
    # 公司详情页url
    compUrl = Field()
    # 公司简称
    compName = Field()
    # 证券代码
    securityCode = Field()
    # 交易所代码
    exchange = Field()
    # 证券简称
    securityName = Field()
    # 上市日期
    theDate = Field()
    # 总股本
    wholeCapital = Field()
    # 流通股本
    circulatingCapital = Field()
    # 公司公告列表页url
    announcementUrl = Field()

在 StockBaseInfoSpider目录下面创建Python package:dbservice,并在此目录下新建DbService.py文件,如下

# -*- coding: utf-8 -*-

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: http://doc.scrapy.org/en/latest/topics/item-pipeline.html
import pymysql

class DbService(object):
    def __init__(self):
        # charset必须设置为utf8,而不能为utf-8
        self.conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='111111', db='spiderdb', charset='utf8')
        self.cursor = self.conn.cursor()
        pass

    def close(self):
        if self.cursor:
            self.cursor.close()
            print '---> close cursor'
        if self.conn:
            self.conn.close()
            print '---> close conn'

    # 批量插入多个item
    def process_items(self, items):
        if items:
            for item in items:
                self.insertItem(item)
            self.conn.commit()
        else:
            print '---> process_items error: items is None'

    # 插入单个item
    def process_item(self, item):
        self.insertItem(item)
        self.conn.commit()

    # 没有加异常处理,请自行添加
    # on duplicate key update 这个写法是以表中的唯一索引unique字段为主,去更新其他的字段,兼顾insert和update功能,即没有唯一索引对应的数据,就insert,有就update
    def insertItem(self, item):
        if item:
            sql = "insert into spider_stock_base_info " \
                  "(comp_code, comp_url, comp_name, security_code, exchange, " \
                  "security_name, the_date, whole_capital, circulating_capital, " \
                  "announcement_url) " \
                  "values ('{0}', '{1}', '{2}', '{3}', '{4}', " \
                  "'{5}', '{6}', {7}, {8}, " \
                  "'{9}') " \
                  "on duplicate key update " \
                  "comp_code=values(comp_code), comp_url=values(comp_url), comp_name=values(comp_name), " \
                  "security_name=values(security_name), the_date=values(the_date), whole_capital=values(whole_capital), circulating_capital=values(circulating_capital), " \
                  "announcement_url=values(announcement_url) "
            sql = sql.format(item['compCode'], item['compUrl'], item['compName'], item['securityCode'], item['exchange'],
                             item['securityName'], item['theDate'], item['wholeCapital'], item['circulatingCapital']
                             , item['announcementUrl'])
            self.cursor.execute(sql)

            print '---> insert ', item['securityCode'], item['exchange'], item['securityName'], 'success'
        else:
            print '---> error:item is None, do not insert!'

在spiders目录下面新建SHStockBaseInfoSpider.py文件,如下

# -*- coding: utf-8 -*-


# 上交所A股列表地址,页面是异步查询加载,不能用常规的爬虫方法
# http://www.sse.com.cn/assortment/stock/list/share/
from scrapy import Spider
from bs4 import BeautifulSoup
from StockBaseInfoSpider.items import StockbaseinfospiderItem
import spynner
import pyquery
from StockBaseInfoSpider.dbservice.DbService import DbService

class SHStockBaseInfoSpider(Spider):
    name = "StockBaseInfoSpider"
    allowed_domains = []
    start_urls = [
        'http://www.sse.com.cn/assortment/stock/list/share/'
    ]

    download_delay = 1

    exchange = 'SH'

    dbService = DbService()

    # 这个地方没有直接解析response,而是用browser重新加载了当前的self.start_urls[0],这个parse方法只是一个爬虫的入口,具体实现在里面
    def parse(self, response):
        browser = spynner.Browser()
        browser.create_webview()
        browser.set_html_parser(pyquery.PyQuery)
        browser.load(self.start_urls[0], 20)
        # 第一次设置为1,是为了加载第一页数据后不触发下一页button的click事件
        self.browserRequest(browser, 1)

    # 分页请求
    def browserRequest(self, browser, nextClick=None):
        print "nextClick:", nextClick
        # nextClick != undefined表示还有下一页数据
        if nextClick != 'undefined':
            # nextClick != 1,说明是第一次爬取,不需要触发下一页button的click事件
            if nextClick != 1:
                # 触发下一页button的click事件
                browser.click("button[class='btn btn-default navbar-btn next-page classPage']")
            try:
                # 等待5秒钟,待页面渲染完毕,有可能5秒钟还不够,需要测试,有可能5秒钟太长
                browser.wait_load(5)
            except:
                print '我也不知道是什么异常,反正不影响运行就行'
            # 解析html页面,提取需要的数据
            self.parseData(browser)
        # else分支,nextClick == 'undefined',说明没有数据可爬取,关闭数据库连接
        else:
            self.dbService.close()
            print 'Done'

    # 解析html页面上的数据
    def parseData(self, browser):
        # 完整的html页面字符串
        body = str(browser.html)
        # 使用美丽汤BeautifulSoup工具解析提取数据
        soup = BeautifulSoup(body, 'html.parser')
        # 下面注释的代码可以将html字符串保存为一个html文件,供分析使用
        # with open('stock_info.html', 'w') as f:
        #     f.write(soup.prettify())
        # 找到页面上主要的数据区域,表格区域
        js_tableT01 = soup.find('div', 'tab-pane active js_tableT01')
        # 获取数据表格
        tdclickable = js_tableT01.find('div', 'table-responsive sse_table_T01 tdclickable')
        # 获取分页
        pagetable = js_tableT01.find('div', 'page-con-table')
        # 获取表格中的tbody
        tbody = tdclickable.find('tbody')
        # 获取tbody中的tr
        trs = tbody.find_all('tr')
        items = []
        # 遍历tr
        for tr in trs:
            # 每个tr中的td
            tds = tr.find_all('td')
            # 遍历td
            if tds:
                tda = tds[0].find('a')
                compCode = tda.get_text().strip()
                compUrl = 'http://www.sse.com.cn' + tda['href']
                compName = tds[1].get_text().strip()
                securityCode = tds[2].get_text().strip()
                securityName = tds[3].get_text().strip()
                theDate = tds[4].get_text().strip()
                wholeCapital = tds[5].get_text().strip()
                circulatingCapital = tds[6].get_text().strip()
                announcementUrl = 'http://www.sse.com.cn' + tds[7].find('a')['href'].strip()

                item = StockbaseinfospiderItem()
                item['compCode'] = compCode
                item['compUrl'] = compUrl
                item['compName'] = compName
                item['securityCode'] = securityCode
                item['exchange'] = self.exchange
                item['securityName'] = securityName
                # 测试发现有些时间数据为-,导致插入数据库异常
                if theDate == '-':
                    theDate = '1970-01-01'
                item['theDate'] = theDate
                item['wholeCapital'] = wholeCapital
                item['circulatingCapital'] = circulatingCapital
                item['announcementUrl'] = announcementUrl
                items.append(item)
        # 批量插入数据库
        self.dbService.process_items(items)

        # 获取分页的button
        buttons = pagetable.find('div', 'visible-xs mobile-page').find_all('button')
        # 下一页的页码
        nextPageNum = 'undefined'
        if buttons and len(buttons) == 2:
            nextPageNum = buttons[1]['page']
        else:
            nextPageNum = 'undefined'
        # 由于页面是异步加载,无刷新分页,所以只能使用spynner触发下一页button的click事件,等待页面加载完毕,继续解析,下面是触发下一次分页操作
        self.browserRequest(browser, nextPageNum)

在StockBaseInfoSpider目录下面新建run.py文件,如下

# -*- coding: utf-8 -*-
from scrapy import cmdline
cmdline.execute('scrapy crawl StockBaseInfoSpider'.split())

新建数据库和表

在本地MySQL服务器上新建数据库spiderdb,在spiderdb上新建表spider_stock_base_info,建表语句如下

CREATE TABLE `spider_stock_base_info` (
	`id` INT(11) NOT NULL AUTO_INCREMENT COMMENT '主键自增',
	`comp_code` VARCHAR(10) NULL DEFAULT NULL COMMENT '公司代码',
	`comp_url` VARCHAR(1000) NULL DEFAULT NULL COMMENT '公司介绍url',
	`comp_name` VARCHAR(100) NULL DEFAULT NULL COMMENT '公司名称',
	`security_code` VARCHAR(10) NULL DEFAULT NULL COMMENT '证券代码',
	`exchange` VARCHAR(10) NULL DEFAULT NULL COMMENT '交易所代码',
	`security_name` VARCHAR(100) NULL DEFAULT NULL COMMENT '证券名称',
	`the_date` DATE NULL DEFAULT NULL COMMENT '上市时间',
	`whole_capital` DECIMAL(10,2) NULL DEFAULT NULL COMMENT '总股本',
	`circulating_capital` DECIMAL(10,2) NULL DEFAULT NULL COMMENT '流通股本',
	`announcement_url` VARCHAR(1000) NULL DEFAULT NULL COMMENT '公告列表url',
	`auto_time` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '自动时间戳',
	PRIMARY KEY (`id`),
	UNIQUE INDEX `security_code` (`security_code`, `exchange`),
	INDEX `auto_time` (`auto_time`)
)
COMMENT='股票基本信息表'
COLLATE='utf8_general_ci'
ENGINE=InnoDB
;

测试代码和数据

在run.py文件上点右键Run运行,查看控制台打印的日志,查看数据库表中的数据,实验成功。

MySQL中保存的数据,如下图

工程源码地址:https://github.com/listen-zhou/StockBaseInfoSpider

由于pycharm无法提交代码到码云,只能提交到github上,源码中有很详细的注释,有不明白的再回帖问我。

遇到的问题及解决方式

1.无刷新异步加载数据,无法通过分页按钮url爬取

上证交易所网站的A股列表页分页是用的异步加载刷新,无法使用常规的爬虫方式获取数据,特别是分页数据。

解决方案:使用spynner工具模拟一个无头的浏览器访问,并模拟鼠标的点击分页按钮事件,实现分页功能,并获取分页的数据,由于无法使用yield Request的方式执行下一页数据的加载,所以只能在一个parse方法里面处理所有的爬虫请求。

2.spynner获取的网页中文乱码

解决方案:经多方查找,终于在一篇博客中发现解决方法

需要将安装的spynner卸载并删除干净,然后从https://pypi.python.org/pypi/spynner下载最新版本的zip包,解压,并按上图中的修改方式修改源码,然后使用Python编译安装,至此spynner安装完毕,就可以重新测试,会发现中文乱码没有了。

3.MySQL数据库插入时中文乱码

解决方案:需要将连接的参数charset设置为utf8,设置为utf-8不行。

 

 

© 著作权归作者所有

L
粉丝 13
博文 28
码字总数 53225
作品 0
宝山
程序员
私信 提问
scrapy-redis之简介,安装

(scrapy_redis框架源码: https://github.com/rmax/scrapy-redis) 1 概念原理 scrapy-redis是一个基于redis的分布式爬虫框架,用于在爬取大量请求数据的情况下,单个主机的处理能力不足问题.(可...

LinQiH
2017/10/21
0
0
xiyouMc/PornHubBot

免责声明:本项目旨在学习Scrapy爬虫框架和MongoDB数据库,不可使用于商业和个人其他意图。若使用不当,均由个人承担。 简介 项目主要是爬取全球最大成人网站PornHub的视频标题、时长、mp4链...

xiyouMc
2017/04/10
0
0
用scrapy-redis爬去新浪-以及把数据存储到mysql\mongo

需求:爬取新浪网导航页(http://news.sina.com.cn/guide/)所有下所有大类、小类、小类里的子链接,以及子链接页面的新闻内容。 准备工作: a.安装redis(windows或者linux) b.安装Redis Des...

丁典
2018/07/17
476
1
Python scrapy 常见问题及解决 【遇到的坑】

1. 爬虫出现Forbidden by robots.txt 解决方法:setting.py ROBOTSTXT_OBEY = True 改成False 原因:scrapy抓包时的输出就能发现,在请求我们设定的url之前,它会先向服务器根目录请求一个t...

littlebob
2018/08/12
0
0
成人网站PornHub爬虫分享

声明:本项目旨在学习Scrapy爬虫框架和MongoDB数据库,不可使用于商业和个人其他意图。若使用不当,均由个人承担。 https://github.com/xiyouMc/PornHubBot 首先科普下 PornHub 是个啥东东?...

xiyouMc
2017/04/10
114
0

没有更多内容

加载失败,请刷新页面

加载更多

DDD(五)

1、引言 之前学习了解了DDD中实体这一概念,那么接下来需要了解的就是值对象、唯一标识。值对象,值就是数字1、2、3,字符串“1”,“2”,“3”,值时对象的特征,对象是一个事物的具体描述...

MrYuZixian
58分钟前
3
0
数据库中间件MyCat

什么是MyCat? 查看官网的介绍是这样说的 一个彻底开源的,面向企业应用开发的大数据库集群 支持事务、ACID、可以替代MySQL的加强版数据库 一个可以视为MySQL集群的企业级数据库,用来替代昂贵...

沉浮_
今天
4
0
解决Mac下VSCode打开zsh乱码

1.乱码问题 iTerm2终端使用Zsh,并且配置Zsh主题,该主题主题需要安装字体来支持箭头效果,在iTerm2中设置这个字体,但是VSCode里这个箭头还是显示乱码。 iTerm2展示如下: VSCode展示如下: 2...

HelloDeveloper
今天
6
0
常用物流快递单号查询接口种类及对接方法

目前快递查询接口有两种方式可以对接,一是和顺丰、圆通、中通、天天、韵达、德邦这些快递公司一一对接接口,二是和快递鸟这样第三方集成接口一次性对接多家常用快递。第一种耗费时间长,但是...

程序的小猿
今天
5
0
Python机器学习之数据探索可视化库yellowbrick

背景介绍 从学sklearn时,除了算法的坎要过,还得学习matplotlib可视化,对我的实践应用而言,可视化更重要一些,然而matplotlib的易用性和美观性确实不敢恭维。陆续使用过plotly、seaborn,...

yeayee
今天
8
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部