首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >基于Scrapy爬取伯乐在线网站

基于Scrapy爬取伯乐在线网站

作者头像
潇洒坤
发布2018-09-10 10:35:13
1.2K0
发布2018-09-10 10:35:13
举报
文章被收录于专栏:简书专栏简书专栏

标题中的英文首字母大写比较规范,但在python实际使用中均为小写。 2018年7月20日笔记 Scrapy官方文档网址:https://doc.scrapy.org/en/latest/topics/selectors.html 网页在chrome浏览器打开,经过谷歌翻译,如下图所示:

图片.png-90.6kB
图片.png-90.6kB

图片.png-90.6kB

环境

IDE(Intergrated development Environment),集成开发环境为jupyter notebook和Pycharm 操作系统Win10 语言及其版本:python3.6

1.选择器

使用Selector初始化方法实例化对象赋值给response变量。 css和extract这2个方法的使用示例如下:

图片.png-8.2kB
图片.png-8.2kB

图片.png-8.2kB

.//和//的区别如下图所示,一般来说要使用.//

图片.png-9.3kB
图片.png-9.3kB

图片.png-9.3kB

xpath和css方法对比,按照标签属性的值来找标签,如下图所示。

图片.png-13kB
图片.png-13kB

图片.png-13kB

2. 伯乐在线网页持久化

2.1 新建爬虫工程

打开cmd或者powershell在其中输入并运行命令,运行结果如下图所示: 新建爬虫工程命令:scrapy startproject BoleSave

图片.png-7.7kB
图片.png-7.7kB

图片.png-7.7kB

进入爬虫工程目录命令:cd BoleSave,运行结果如下图所示:

图片.png-11.8kB
图片.png-11.8kB

图片.png-11.8kB

新建爬虫文件命令: scrapy genspider save blog.jobbole.com,运行结果如下图所示:

图片.png-12.8kB
图片.png-12.8kB

图片.png-12.8kB

2.2 在Pycharm中导入工程

导入工程的按钮位置如下图所示:

图片.png-25.6kB
图片.png-25.6kB

图片.png-25.6kB

选中工程文件夹,然后点击OK,如下图所示:

图片.png-15.3kB
图片.png-15.3kB

图片.png-15.3kB

工程文件夹的结构如下图所示:

图片.png-4.8kB
图片.png-4.8kB

图片.png-4.8kB

2.3 编辑save.py文件

网页持久化只需要编辑爬虫文件就可以,下面是save.py文件的代码。 第21行dirName变量的值可以设置网页文件保存的位置,例如: dirName = "d:/saveWebPage"将网页文件保存在D盘的saveWebPage文件夹中。 可以根据个人情况进行修改,不建议将其设置为工程所在文件夹,因为可能导致Pycharm卡顿。

# -*- coding: utf-8 -*-
import scrapy
import os
import re

def reFind(pattern,sourceStr,nth=1):
    if len(re.findall(pattern,sourceStr)) >= nth:
        return re.findall(pattern,sourceStr)[nth-1]
    else:
        return 1

class SaveSpider(scrapy.Spider):
    name = 'save'
    allowed_domains = ['blog.jobbole.com']
    start_urls = []
    url_before = "http://blog.jobbole.com/all-posts/page/{}/"
    for i in range(1,560):
        start_urls.append(url_before.format(i))

    def parse(self, response):
        dirName = "d:/saveWebPage"
        if not os.path.isdir(dirName):
            os.mkdir(dirName)
        url = response.url
        page_id = int(reFind("\d+", url))
        html = response.text
        fileName = "%s/%03d.html" % (dirName, page_id)
        with open(fileName, 'w', encoding="utf-8") as file:
            file.write(html)
        print("目录页面第%d页被存放到%s目录中的%03d.html文件中" %
              (page_id,dirName,page_id))

2.4 运行结果

运行命令:scrapy crawl save,此命令运行时cmd进入的目录必须在爬虫工程内 运行结果如下图所示:

图片.png-42.9kB
图片.png-42.9kB

图片.png-42.9kB

从上图中可以观察爬虫的开始时间start_time和finish_time相差为17秒,即从网站中持久化559张网页用时17秒。 本文的测试环境是利用电信4G手机USB连接电脑分享网络,测试时间为2018年7月21日。 保存网页文件的文件夹如下图所示:

图片.png-42.7kB
图片.png-42.7kB

图片.png-42.7kB

3.解析伯乐在线网页

已经将网站上的网页保存为本地html文件,并将559个文件打包为压缩文件。 压缩文件下载链接: https://pan.baidu.com/s/1ZI2zBkxw7z4vaYYQIZsmdQ 密码: qtp3 解析后的数据存到mysql数据库中,需要先创建数据库bole 采用了数据库连接池,异步多线程操作数据库可以提高效率。

3.1新建爬虫工程

创建爬虫工程命令:scrapy startproject BoleParse 进入爬虫工程目录:cd ./BoleParse/ 创建爬虫文件命令: scrapy genspider parse blog.jobbole.com

3.2 编辑items.py文件

import scrapy
from scrapy import Field

class BolearticleItem(scrapy.Item):
    id = Field()
    title = Field()
    publishTime = Field()
    category = Field()
    digest = Field()
    detailUrl = Field()
    imgUrl = Field()

3.3 编辑parse.py文件

import scrapy
from ..items import BoleparseItem
import re
import os

def reFind(pattern,sourceStr,nth=1):
    if len(re.findall(pattern,sourceStr)) >= nth:
        return re.findall(pattern,sourceStr)[nth-1]
    else:
        return 1

class ParseSpider(scrapy.Spider):
    name = 'parse'
    start_urls = []
    baseUrl = "file:///%s/saveWebPage/%03d.html"
    for i in range(560):
        start_urls.append(baseUrl %(os.getcwd(),i))

    def parse(self, response):
        def find(xpath, pNode=response):
            if len(pNode.xpath(xpath)):
                return pNode.xpath(xpath).extract()[0]
            else:
                return ''
        article_list = response.xpath("//div[@class='post floated-thumb']")
        page_id_str = reFind("saveWebPage/(\d+).html", response.url)
        page_id = int(page_id_str)
        count = 0
        for article in article_list:
            count += 1
            item = BoleparseItem()
            item['id'] = (page_id - 1) * 20 + count
            item['title'] = find("div[@class='post-meta']/p[1]/a/@title", article)
            pTagStr = find("div[@class='post-meta']/p", article)
            item['publishTime'] = re.search("\d+/\d+/\d+", pTagStr).group(0)
            item['category'] = find("div[@class='post-meta']/p/a[2]/text()", article)
            item['digest'] = find("div[@class='post-meta']/span/p/text()", article)
            item['imgUrl'] = find("div[@class='post-thumb']/a/img/@src", article)
            item['detailUrl'] = find("div[@class='post-meta']/p/a[1]/@href", article)
            yield item

3.3 编辑pipelines.py文件

使用pymysql库将每一条文章信息item导入mysql数据库 下面一段代码需要修改2处:1.第4行的数据库名;2.第8行的数据库连接密码。 第24行default charset=utf8mb4创建表默认编码为utf8mb4,因为插入字符可能是4个字节编码。 第29、30行if len(item['imgUrl']) >= 200:item.pop('imgUrl')的作用: 防止图片是base64编码长度过大,遇到此类型的值则丢弃此字段。 通过这2个设置,增加了代码的健壮性,能够保证11172条数据都插入到数据库中。

import pymysql
from time import time

def getConn(database ="bole"):
    args = dict(
        host = 'localhost',
        user = 'root',
        passwd = '... your password',
        charset = 'utf8mb4',
        db = database
    )
    return pymysql.connect(**args)

class BoleparsePipeline(object):
    startTime = time()
    conn = getConn()
    cursor = conn.cursor()
    drop_sql = "drop table if exists article"
    cursor.execute(drop_sql)
    conn.commit()
    create_sql = "create table article(id int primary key," \
                 "title varchar(200),publishtime varchar(30)," \
                 "category varchar(30),digest text," \
                 "detailUrl varchar(200),imgUrl varchar(200))default charset = utf8mb4;"
    cursor.execute(create_sql)
    conn.commit()

    def process_item(self, item, spider):
        if len(item['imgUrl']) >= 200:
            item.pop('imgUrl')
        fieldStr = ','.join(['`%s`' % k for k in item.keys()])
        valuesStr = ','.join(['"%s"' % v for v in item.values()])
        insert_sql = "insert into article(%s) values(%s)" % (fieldStr, valuesStr)
        self.cursor.execute(insert_sql)
        self.conn.commit()
        return item

    def close_spider(self, spider):
        print("程序总共运行%.2f秒" % (time() - self.startTime))

3.4 编辑settings文件

关键点是最后3行要开启管道,CONCURRENT_REQUESTS变量设置为96能够较好利用多线程性能 CONCURRENT_ITEMS设置为200能够加快并发管道处理item的速度。 ROBOTSTXT_OBEY设置为False,意思是不遵守爬虫协议,也称机器人协议。如果设置为True,即遵守爬虫协议,则可能访问受限。

BOT_NAME = 'BoleParse'
SPIDER_MODULES = ['BoleParse.spiders']
NEWSPIDER_MODULE = 'BoleParse.spiders'
ROBOTSTXT_OBEY = False
CONCURRENT_REQUESTS = 96
CONCURRENT_ITEMS = 200
ITEM_PIPELINES = {
   'BoleParse.pipelines.BoleparsePipeline': 300
}

3.5 放置持久化文件

saveWebPage文件夹必须和启动cmd时处在相同的文件夹,只有这样才能运行成功。 如下图所示,powershell现在进入的目录是C:\Users\Administrator\Desktop\伯乐\BoleParse, 则saveWebPage文件夹也必须在C:\Users\Administrator\Desktop\伯乐\BoleParse中。 注意:读者的路径与本文不同;运行命令前建议先关闭Pycharm,否则可能卡顿

图片.png-13.6kB
图片.png-13.6kB

图片.png-13.6kB

3.6 运行结果

程序运行结束后,查询插入数据的总条数,如下图所示:

图片.png-2.1kB
图片.png-2.1kB

图片.png-2.1kB

数据库表中数据查看如下图所示:

图片.png-117.1kB
图片.png-117.1kB

图片.png-117.1kB

插入数据总共用时66.51秒,如下图所示:

图片.png-42.9kB
图片.png-42.9kB

图片.png-42.9kB

3.7 数据库连接池

进行此步骤时需要先把pipelines.py文件中的代码清空,然后把下面的代码插入其中。 数据库连接池方式进行数据库操作效率更高,因为是异步多线程运行,效率提高40%左右。 用twisted.enterprise.adbapi方法初始化一个数据库连接池对象。 该方法需要7个参数,其中dbapiName、cursorclass这2个和数据连接用的库有关, 其他5个参数是数据库连接设置,host、db、user、passwd、charset。 dbpool.runInteraction里面传入的第1个参数是函数对象,后面参数不定长。

from twisted.enterprise import adbapi
import pymysql
import time

class BoleparsePipeline(object):
    def __init__(self):
        params = dict(
            dbapiName = 'pymysql',
            cursorclass=pymysql.cursors.DictCursor,
            host = 'localhost',
            db = 'bole',
            user = 'root',
            passwd = '...your password',
            charset = 'utf8',
        )
        self.dbpool = adbapi.ConnectionPool(**params)
        self.startTime = time.time()
        self.dbpool.runInteraction(self.createTable)

    def createTable(self, cursor):
        drop_sql = "drop table if exists article"
        cursor.execute(drop_sql)
        create_sql = "create table article(id int primary key," \
                     "title varchar(200),publishtime varchar(30)," \
                     "category varchar(30),digest text," \
                     "detailUrl varchar(200),imgUrl varchar(200))" \
                     "default charset = utf8mb4;"
        cursor.execute(create_sql)

    def process_item(self, item, spider):
        self.dbpool.runInteraction(self.insert,item)
        return item

    def insert(self, cursor, item):
        try:
            if len(item['imgUrl']) >= 200:
                item.pop('imgUrl')
            fieldStr = ','.join(['`%s`' % k for k in item.keys()])
            valuesStr = ','.join(['"%s"' % v for v in item.values()])
            insert_sql = "insert into article(%s) values(%s)" % (fieldStr, valuesStr)
            cursor.execute(insert_sql)
        except Exception as e:
            with open("insert.log",'a+') as file:
                datetime = time.strftime('%Y-%m-%d %H:%M:%S')
                logStr = "%s log:插入第%d条数据发生异常\nreason:%s\n"
                file.write(logStr %(datetime,item['id'],str(e)))

    def close_spider(self, spider):
        print("程序总共运行%.2f秒" % (time.time() - self.startTime))

从下图中可以看出插入数据到mysql数据库中总共用时45.18秒 所以使用数据库连接池效率提高66.51/45.18-1=47%

图片.png-41.6kB
图片.png-41.6kB

图片.png-41.6kB

with open("insert.log",'a+') as file,在日志中一般读写方式使用a+ 数据库插入11171条数据,有1条插入数据库失败,查看错误日志:

图片.png-6kB
图片.png-6kB

图片.png-6kB

4.查看数据库缺少条目

先从数据库中取出所有条目的id,赋值给id_list result = set(id_list)^set(range(1,11173))第20行代码通过2个集合取差集找出缺少的条目。

import pymysql

def getConn(database ="bole"):
    args = dict(
        host = 'localhost',
        user = 'root',
        passwd = '...your password',
        charset = 'utf8',
        db = database
    )
    return pymysql.connect(**args)

if __name__ == "__main__":
    conn = getConn()
    cursor = conn.cursor()
    sql = "select id from article"
    cursor.execute(sql)
    result = cursor.fetchall()
    id_list = [k[0] for k in result]
    result = set(id_list)^set(range(1,11173))
    print(result)
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018.07.21 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 环境
  • 1.选择器
  • 2. 伯乐在线网页持久化
    • 2.1 新建爬虫工程
      • 2.2 在Pycharm中导入工程
        • 2.3 编辑save.py文件
          • 2.4 运行结果
          • 3.解析伯乐在线网页
            • 3.1新建爬虫工程
              • 3.2 编辑items.py文件
                • 3.3 编辑parse.py文件
                  • 3.3 编辑pipelines.py文件
                    • 3.4 编辑settings文件
                      • 3.5 放置持久化文件
                        • 3.6 运行结果
                          • 3.7 数据库连接池
                          • 4.查看数据库缺少条目
                          领券
                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档