基于Scrapy爬取伯樂(lè)在線網(wǎng)站

標(biāo)題中的英文首字母大寫比較規(guī)范酸钦,但在python實(shí)際使用中均為小寫响驴。
2018年7月20日筆記
Scrapy官方文檔網(wǎng)址:https://doc.scrapy.org/en/latest/topics/selectors.html
網(wǎng)頁(yè)在chrome瀏覽器打開(kāi)豌注,經(jīng)過(guò)谷歌翻譯兔仰,如下圖所示:

圖片.png-90.6kB
圖片.png-90.6kB

環(huán)境

IDE(Intergrated development Environment)领猾,集成開(kāi)發(fā)環(huán)境為jupyter notebook和Pycharm
操作系統(tǒng)Win10
語(yǔ)言及其版本:python3.6

1.選擇器

使用Selector初始化方法實(shí)例化對(duì)象賦值給response變量瑰妄。
css和extract這2個(gè)方法的使用示例如下:


圖片.png-8.2kB
圖片.png-8.2kB

.//和//的區(qū)別如下圖所示陷嘴,一般來(lái)說(shuō)要使用.//


圖片.png-9.3kB
圖片.png-9.3kB

xpath和css方法對(duì)比,按照標(biāo)簽屬性的值來(lái)找標(biāo)簽间坐,如下圖所示灾挨。
圖片.png-13kB
圖片.png-13kB

2. 伯樂(lè)在線網(wǎng)頁(yè)持久化

2.1 新建爬蟲工程

打開(kāi)cmd或者powershell在其中輸入并運(yùn)行命令,運(yùn)行結(jié)果如下圖所示:
新建爬蟲工程命令:scrapy startproject BoleSave

圖片.png-7.7kB
圖片.png-7.7kB

進(jìn)入爬蟲工程目錄命令:cd BoleSave竹宋,運(yùn)行結(jié)果如下圖所示:
圖片.png-11.8kB
圖片.png-11.8kB

新建爬蟲文件命令: scrapy genspider save blog.jobbole.com劳澄,運(yùn)行結(jié)果如下圖所示:
圖片.png-12.8kB
圖片.png-12.8kB

2.2 在Pycharm中導(dǎo)入工程

導(dǎo)入工程的按鈕位置如下圖所示:


圖片.png-25.6kB
圖片.png-25.6kB

選中工程文件夾,然后點(diǎn)擊OK,如下圖所示:


圖片.png-15.3kB
圖片.png-15.3kB

工程文件夾的結(jié)構(gòu)如下圖所示:
圖片.png-4.8kB
圖片.png-4.8kB

2.3 編輯save.py文件

網(wǎng)頁(yè)持久化只需要編輯爬蟲文件就可以蜈七,下面是save.py文件的代碼秒拔。
第21行dirName變量的值可以設(shè)置網(wǎng)頁(yè)文件保存的位置,例如:
dirName = "d:/saveWebPage"將網(wǎng)頁(yè)文件保存在D盤的saveWebPage文件夾中飒硅。
可以根據(jù)個(gè)人情況進(jìn)行修改砂缩,不建議將其設(shè)置為工程所在文件夾,因?yàn)榭赡軐?dǎo)致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("目錄頁(yè)面第%d頁(yè)被存放到%s目錄中的%03d.html文件中" %
              (page_id,dirName,page_id))

2.4 運(yùn)行結(jié)果

運(yùn)行命令:scrapy crawl save梯轻,此命令運(yùn)行時(shí)cmd進(jìn)入的目錄必須在爬蟲工程內(nèi)
運(yùn)行結(jié)果如下圖所示:

圖片.png-42.9kB
圖片.png-42.9kB

從上圖中可以觀察爬蟲的開(kāi)始時(shí)間start_time和finish_time相差為17秒,即從網(wǎng)站中持久化559張網(wǎng)頁(yè)用時(shí)17秒尽棕。
本文的測(cè)試環(huán)境是利用電信4G手機(jī)USB連接電腦分享網(wǎng)絡(luò)喳挑,測(cè)試時(shí)間為2018年7月21日。
保存網(wǎng)頁(yè)文件的文件夾如下圖所示:
圖片.png-42.7kB
圖片.png-42.7kB

3.解析伯樂(lè)在線網(wǎng)頁(yè)

已經(jīng)將網(wǎng)站上的網(wǎng)頁(yè)保存為本地html文件,并將559個(gè)文件打包為壓縮文件伊诵。
壓縮文件下載鏈接: https://pan.baidu.com/s/1ZI2zBkxw7z4vaYYQIZsmdQ 密碼: qtp3
解析后的數(shù)據(jù)存到mysql數(shù)據(jù)庫(kù)中单绑,需要先創(chuàng)建數(shù)據(jù)庫(kù)bole
采用了數(shù)據(jù)庫(kù)連接池,異步多線程操作數(shù)據(jù)庫(kù)可以提高效率曹宴。

3.1新建爬蟲工程

創(chuàng)建爬蟲工程命令:scrapy startproject BoleParse
進(jìn)入爬蟲工程目錄:cd ./BoleParse/
創(chuàng)建爬蟲文件命令: 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("http://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庫(kù)將每一條文章信息item導(dǎo)入mysql數(shù)據(jù)庫(kù)
下面一段代碼需要修改2處:1.第4行的數(shù)據(jù)庫(kù)名搂橙;2.第8行的數(shù)據(jù)庫(kù)連接密碼。
第24行default charset=utf8mb4創(chuàng)建表默認(rèn)編碼為utf8mb4,因?yàn)椴迦胱址赡苁?個(gè)字節(jié)編碼笛坦。
第29区转、30行if len(item['imgUrl']) >= 200:item.pop('imgUrl')的作用:
防止圖片是base64編碼長(zhǎng)度過(guò)大,遇到此類型的值則丟棄此字段版扩。
通過(guò)這2個(gè)設(shè)置废离,增加了代碼的健壯性,能夠保證11172條數(shù)據(jù)都插入到數(shù)據(jù)庫(kù)中礁芦。

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("程序總共運(yùn)行%.2f秒" % (time() - self.startTime))

3.4 編輯settings文件

關(guān)鍵點(diǎn)是最后3行要開(kāi)啟管道蜻韭,CONCURRENT_REQUESTS變量設(shè)置為96能夠較好利用多線程性能
CONCURRENT_ITEMS設(shè)置為200能夠加快并發(fā)管道處理item的速度。
ROBOTSTXT_OBEY設(shè)置為False,意思是不遵守爬蟲協(xié)議柿扣,也稱機(jī)器人協(xié)議肖方。如果設(shè)置為True,即遵守爬蟲協(xié)議未状,則可能訪問(wèn)受限俯画。

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文件夾必須和啟動(dòng)cmd時(shí)處在相同的文件夾,只有這樣才能運(yùn)行成功司草。
如下圖所示活翩,powershell現(xiàn)在進(jìn)入的目錄是C:\Users\Administrator\Desktop\伯樂(lè)\BoleParse,
則saveWebPage文件夾也必須在C:\Users\Administrator\Desktop\伯樂(lè)\BoleParse中翻伺。
注意:讀者的路徑與本文不同材泄;運(yùn)行命令前建議先關(guān)閉Pycharm,否則可能卡頓

圖片.png-13.6kB
圖片.png-13.6kB

3.6 運(yùn)行結(jié)果

程序運(yùn)行結(jié)束后吨岭,查詢插入數(shù)據(jù)的總條數(shù)拉宗,如下圖所示:


圖片.png-2.1kB
圖片.png-2.1kB

數(shù)據(jù)庫(kù)表中數(shù)據(jù)查看如下圖所示:


圖片.png-117.1kB
圖片.png-117.1kB

插入數(shù)據(jù)總共用時(shí)66.51秒,如下圖所示:
圖片.png-42.9kB
圖片.png-42.9kB

3.7 數(shù)據(jù)庫(kù)連接池

進(jìn)行此步驟時(shí)需要先把pipelines.py文件中的代碼清空辣辫,然后把下面的代碼插入其中旦事。
數(shù)據(jù)庫(kù)連接池方式進(jìn)行數(shù)據(jù)庫(kù)操作效率更高,因?yàn)槭钱惒蕉嗑€程運(yùn)行急灭,效率提高40%左右姐浮。
用twisted.enterprise.adbapi方法初始化一個(gè)數(shù)據(jù)庫(kù)連接池對(duì)象。
該方法需要7個(gè)參數(shù)葬馋,其中dbapiName卖鲤、cursorclass這2個(gè)和數(shù)據(jù)連接用的庫(kù)有關(guān)肾扰,
其他5個(gè)參數(shù)是數(shù)據(jù)庫(kù)連接設(shè)置,host蛋逾、db集晚、user、passwd区匣、charset偷拔。
dbpool.runInteraction里面?zhèn)魅氲牡?個(gè)參數(shù)是函數(shù)對(duì)象,后面參數(shù)不定長(zhǎng)亏钩。

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條數(shù)據(jù)發(fā)生異常\nreason:%s\n"
                file.write(logStr %(datetime,item['id'],str(e)))

    def close_spider(self, spider):
        print("程序總共運(yùn)行%.2f秒" % (time.time() - self.startTime))

從下圖中可以看出插入數(shù)據(jù)到mysql數(shù)據(jù)庫(kù)中總共用時(shí)45.18秒
所以使用數(shù)據(jù)庫(kù)連接池效率提高66.51/45.18-1=47%

圖片.png-41.6kB
圖片.png-41.6kB

with open("insert.log",'a+') as file莲绰,在日志中一般讀寫方式使用a+
數(shù)據(jù)庫(kù)插入11171條數(shù)據(jù),有1條插入數(shù)據(jù)庫(kù)失敗姑丑,查看錯(cuò)誤日志:
圖片.png-6kB
圖片.png-6kB

4.查看數(shù)據(jù)庫(kù)缺少條目

先從數(shù)據(jù)庫(kù)中取出所有條目的id钉蒲,賦值給id_list
result = set(id_list)^set(range(1,11173))第20行代碼通過(guò)2個(gè)集合取差集找出缺少的條目。

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)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末彻坛,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子踏枣,更是在濱河造成了極大的恐慌昌屉,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,029評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件茵瀑,死亡現(xiàn)場(chǎng)離奇詭異间驮,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)马昨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,395評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門竞帽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人鸿捧,你說(shuō)我怎么就攤上這事屹篓。” “怎么了匙奴?”我有些...
    開(kāi)封第一講書人閱讀 157,570評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵堆巧,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我泼菌,道長(zhǎng)谍肤,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 56,535評(píng)論 1 284
  • 正文 為了忘掉前任哗伯,我火速辦了婚禮荒揣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘焊刹。我一直安慰自己系任,他們只是感情好恳蹲,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,650評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著赋除,像睡著了一般阱缓。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上举农,一...
    開(kāi)封第一講書人閱讀 49,850評(píng)論 1 290
  • 那天荆针,我揣著相機(jī)與錄音,去河邊找鬼颁糟。 笑死航背,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的棱貌。 我是一名探鬼主播玖媚,決...
    沈念sama閱讀 39,006評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼婚脱!你這毒婦竟也來(lái)了今魔?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 37,747評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤障贸,失蹤者是張志新(化名)和其女友劉穎错森,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體篮洁,經(jīng)...
    沈念sama閱讀 44,207評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡涩维,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,536評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了袁波。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瓦阐。...
    茶點(diǎn)故事閱讀 38,683評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖篷牌,靈堂內(nèi)的尸體忽然破棺而出睡蟋,到底是詐尸還是另有隱情,我是刑警寧澤枷颊,帶...
    沈念sama閱讀 34,342評(píng)論 4 330
  • 正文 年R本政府宣布薄湿,位于F島的核電站,受9級(jí)特大地震影響偷卧,放射性物質(zhì)發(fā)生泄漏豺瘤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,964評(píng)論 3 315
  • 文/蒙蒙 一听诸、第九天 我趴在偏房一處隱蔽的房頂上張望坐求。 院中可真熱鬧,春花似錦晌梨、人聲如沸桥嗤。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,772評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)泛领。三九已至荒吏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間渊鞋,已是汗流浹背绰更。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,004評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留锡宋,地道東北人儡湾。 一個(gè)月前我還...
    沈念sama閱讀 46,401評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像执俩,于是被迫代替她去往敵國(guó)和親徐钠。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,566評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容