基于Scrapy爬取伯樂在線網(wǎng)站(進(jìn)階版)

標(biāo)題中的英文首字母大寫比較規(guī)范本涕,但在python實際使用中均為小寫放典。
爬取伯樂在線網(wǎng)站所有文章的詳情頁面

1.網(wǎng)頁持久化

1.1 新建爬蟲工程

新建爬蟲工程命令:scrapy startproject BoleSave2


image.png

進(jìn)入爬蟲工程目錄命令:cd BoleSave2
新建爬蟲文件命令:scrapy genspider save blog.jobbole.com

1.2 編輯save.py文件

網(wǎng)頁持久化只需要編輯爬蟲文件就可以笼裳,下面是save.py文件的代碼刊头。
第13行dirName變量的值可以設(shè)置網(wǎng)頁文件保存的位置骄噪,例如:
dirName = "d:/saveWebPage"將網(wǎng)頁文件保存在D盤的saveWebPage文件夾中刻盐。
可以根據(jù)個人情況進(jìn)行修改掏膏,不能將其設(shè)置為工程所在文件夾,因為Pycharm對工程內(nèi)大量新文件進(jìn)行索引會導(dǎo)致卡頓敦锌。

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

def saveWebPage(response,id,prefix):
    # 持久化目錄頁面
    dirName = "d:/saveWebPage2"
    if not os.path.isdir(dirName):
        os.mkdir(dirName)
    html = response.text
    fileName = "%s%05d.html" %(prefix,id)
    filePath = "%s/%s" %(dirName, fileName)
    with open(filePath, 'w', encoding="utf-8") as file:
        file.write(html)
        print("網(wǎng)頁持久化保存為%s文件夾中的%s文件" %(dirName,fileName))

class SaveSpider(scrapy.Spider):
    name = 'save'
    allowed_domains = ['blog.jobbole.com']
    start_urls = ['http://blog.jobbole.com/all-posts/']

    def parse(self, response):
        pageNum = response.xpath("http://a[@class='page-numbers']/text()")[-1].extract()
        for i in range(1, int(pageNum) + 1):
            url = "http://blog.jobbole.com/all-posts/page/{}/".format(i)
            yield scrapy.Request(url, callback=self.parse1)

    def parse1(self, response):
        page_id = int(reFind("\d+", response.url))
        saveWebPage(response,page_id,'directory')
        #獲得詳情頁面的鏈接馒疹,并調(diào)用下一級解析函數(shù)
        article_list = response.xpath("http://div[@class='post floated-thumb']")
        count = 0
        for article in article_list:
            url = article.xpath("div[@class='post-meta']/p/a[1]/@href").extract_first()
            count += 1
            article_id = (page_id - 1) * 20 + count
            yield scrapy.Request(url,self.parse2,meta={'id':article_id})

    def parse2(self, response):
        saveWebPage(response,response.meta['id'],'detail')

1.3 編輯settings.py文件

改變并發(fā)請求數(shù)量,取消變量CONCURRENT_REQUESTS的注釋乙墙,并改變值為96颖变。
CONCURRENT_REQUESTS = 96

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

運(yùn)行命令:scrapy crawl save
559個目錄頁面生均,11172個詳情頁面,兩種頁面相加共有11731個頁面悼做。
而網(wǎng)頁持久化保存的文件個數(shù)也是11731個疯特,說明已經(jīng)完成頁面持久化。


image.png

從下圖中可以看出開始時間與結(jié)束時間相差12分鐘肛走,則11731個頁面持久化耗時12分鐘漓雅。
持久化速度:977頁/分,16.29頁/秒


image.png

2.解析伯樂在線文章詳情頁面

已經(jīng)把11731個網(wǎng)頁文件打包成一個壓縮文件朽色,下載鏈接: https://pan.baidu.com/s/19MDHdwrqrSRTEgVWA9fMzg 密碼: x7nk

2.1 新建爬蟲工程

新建爬蟲工程命令:scrapy startproject BoleParse2
進(jìn)入爬蟲工程目錄命令:cd BoleParse2
新建爬蟲文件命令:scrapy genspider parse blog.jobbole.com

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

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


image.png

選中工程文件夾邻吞,然后點擊OK,如下圖所示:


image.png

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

2.3 編寫items.py文件

共有12個字段,文章識別碼id葫男、標(biāo)題title抱冷、發(fā)布時間publishTime、分類category梢褐、摘要digest旺遮、圖片鏈接imgUrl、詳情鏈接detailUrl盈咳、原文出處originalSource耿眉、內(nèi)容content、點贊數(shù)favourNumber鱼响、收藏數(shù)collectNumber鸣剪、評論數(shù)commentNumber。

import scrapy
from scrapy import Field

class Boleparse2Item(scrapy.Item):
    id = Field()
    title = Field()
    publishTime = Field()
    category = Field()
    digest = Field()
    imgUrl = Field()
    detailUrl = Field()
    originalSource = Field()
    content = Field()
    favourNumber = Field()
    collectNumber = Field()
    commentNumber = Field()

2.4 編寫parse.py文件

parse函數(shù)解析目錄頁面丈积,得到7個字段的值添加進(jìn)item中筐骇,并通過response攜帶meta傳遞給下一級解析函數(shù)。
parse2函數(shù)解析詳情頁面江滨,通過item = response.meta['item']得到已經(jīng)解析一部分內(nèi)容的item铛纬,再對網(wǎng)頁解析得到剩余的5個字段跃脊,最后yield item將item傳給管道進(jìn)行處理谎砾。

注意:修改第13行變量dirName的值

import scrapy
import re
from ..items import Boleparse2Item

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

class ArticleSpider(scrapy.Spider):
    name = 'parse'
    dirName = "E:/saveWebPage2"
    start_urls = []
    for i in range(1,560):
        fileName = "directory%05d.html" %i
        filePath = "file:///%s/%s" %(dirName,fileName)
        start_urls.append(filePath)

    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']")
        pattern = self.dirName + "/directory(\d+).html"
        page_id_str = reFind(pattern,response.url)
        page_id = int(page_id_str)
        count = 0
        for article in article_list:
            count += 1
            item = Boleparse2Item()
            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)
            fileName = "detail%05d.html" %item['id']
            nextUrl = "file:///%s/%s" %(self.dirName,fileName)
            yield scrapy.Request(nextUrl,callback=self.parse1,meta={'item':item})

    def parse1(self, response):
        def find(xpath, pNode=response):
            if len(pNode.xpath(xpath)):
                return pNode.xpath(xpath).extract()[0]
            else:
                return ''
        item = response.meta['item']
        item['originalSource'] = find("http://div[@class='copyright-area']"
                                      "/a[@target='_blank']/@href")
        item['content'] = find("http://div[@class='entry']")
        item['favourNumber'] = find("http://h10/text()")
        item['collectNumber'] = find("http://div[@class='post-adds']"\
                    "/span[2]/text()").strip("收藏").strip()
        commentStr = find("http://a[@href='#article-comment']/span")
        item['commentNumber'] = reFind("(\d+)\s評論",commentStr)
        yield item

2.5 編寫pipelines.py文件

采用數(shù)據(jù)庫連接池提高往數(shù)據(jù)庫中插入數(shù)據(jù)的效率韧衣。
下面代碼有2個地方要修改:1.數(shù)據(jù)庫名僚楞;2.連接數(shù)據(jù)庫的密碼。
設(shè)置數(shù)據(jù)庫編碼方式掀亥,default charset=utf8mb4創(chuàng)建表默認(rèn)編碼為utf8mb4,因為插入字符可能是4個字節(jié)編碼。
item['content'] = my_b64encode(item['content'])將網(wǎng)頁內(nèi)容進(jìn)行base64編碼防止發(fā)生異常。

from twisted.enterprise import adbapi
import pymysql
import time
import os
import base64

def my_b64encode(content):
    byteStr = content.encode("utf-8")
    encodeStr = base64.b64encode(byteStr)
    return encodeStr.decode("utf-8")

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

    def createTable(self, cursor):
        drop_sql = "drop table if exists %s" %self.tableName
        cursor.execute(drop_sql)
        create_sql = "create table %s(id int primary key," \
                     "title varchar(200),publishtime varchar(30)," \
                     "category varchar(30),digest text," \
                     "imgUrl varchar(200),detailUrl varchar(200)," \
                     "originalSource varchar(500),content mediumtext," \
                     "favourNumber varchar(20)," \
                     "collectNumber varchar(20)," \
                     "commentNumber varchar(20)) " \
                     "default charset = utf8mb4" %self.tableName
        cursor.execute(create_sql)
        self.dbpool.connect().commit()

    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')
            item['content'] = my_b64encode(item['content'])
            fieldStr = ','.join(['`%s`' % k for k in item.keys()])
            valuesStr = ','.join(['"%s"' % v for v in item.values()])
            insert_sql = "insert into %s(%s) values(%s)"\
                         % (self.tableName,fieldStr, valuesStr)
            cursor.execute(insert_sql)
            print("往mysql數(shù)據(jù)庫中插入第%d條數(shù)據(jù)成功" %item['id'])
        except Exception as e:
            if not os.path.isdir("Log"):
                os.mkdir("Log")
            filePath = "Log/" + time.strftime('%Y-%m-%d-%H-%M.log')
            with open(filePath, '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))

2.6 編寫settings.py文件

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

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

運(yùn)行命令:scrapy crawl parse


image.png

從上圖可以看出惹挟,插入數(shù)據(jù)總共需要花費(fèi)420秒,即25條/秒缝驳,1558條/分连锯。


image.png

從上圖可以看出插入數(shù)據(jù)總共使用硬盤容量679.5M归苍,條數(shù)共11172條,成功插入每一條數(shù)據(jù)运怖。

3.查找插入異常原因

mysql中查看字符集命令:show variables like "character%"

image.png

content中有組合字符\"導(dǎo)致發(fā)生SQL syntax error
image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末拼弃,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子摇展,更是在濱河造成了極大的恐慌吻氧,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,948評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件咏连,死亡現(xiàn)場離奇詭異盯孙,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)祟滴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評論 3 385
  • 文/潘曉璐 我一進(jìn)店門振惰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人垄懂,你說我怎么就攤上這事骑晶。” “怎么了草慧?”我有些...
    開封第一講書人閱讀 157,490評論 0 348
  • 文/不壞的土叔 我叫張陵桶蛔,是天一觀的道長。 經(jīng)常有香客問我冠蒋,道長羽圃,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,521評論 1 284
  • 正文 為了忘掉前任抖剿,我火速辦了婚禮朽寞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘斩郎。我一直安慰自己脑融,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,627評論 6 386
  • 文/花漫 我一把揭開白布缩宜。 她就那樣靜靜地躺著肘迎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪锻煌。 梳的紋絲不亂的頭發(fā)上妓布,一...
    開封第一講書人閱讀 49,842評論 1 290
  • 那天,我揣著相機(jī)與錄音宋梧,去河邊找鬼匣沼。 笑死,一個胖子當(dāng)著我的面吹牛捂龄,可吹牛的內(nèi)容都是我干的释涛。 我是一名探鬼主播加叁,決...
    沈念sama閱讀 38,997評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼唇撬!你這毒婦竟也來了它匕?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,741評論 0 268
  • 序言:老撾萬榮一對情侶失蹤窖认,失蹤者是張志新(化名)和其女友劉穎豫柬,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體耀态,經(jīng)...
    沈念sama閱讀 44,203評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡轮傍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,534評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了首装。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片创夜。...
    茶點故事閱讀 38,673評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖仙逻,靈堂內(nèi)的尸體忽然破棺而出驰吓,到底是詐尸還是另有隱情,我是刑警寧澤系奉,帶...
    沈念sama閱讀 34,339評論 4 330
  • 正文 年R本政府宣布檬贰,位于F島的核電站,受9級特大地震影響缺亮,放射性物質(zhì)發(fā)生泄漏翁涤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,955評論 3 313
  • 文/蒙蒙 一萌踱、第九天 我趴在偏房一處隱蔽的房頂上張望葵礼。 院中可真熱鬧,春花似錦并鸵、人聲如沸鸳粉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,770評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽届谈。三九已至,卻和暖如春弯汰,著一層夾襖步出監(jiān)牢的瞬間艰山,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,000評論 1 266
  • 我被黑心中介騙來泰國打工咏闪, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留程剥,地道東北人。 一個月前我還...
    沈念sama閱讀 46,394評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像织鲸,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子溪胶,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,562評論 2 349

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