Scrapy爬取伯樂在線

Scrapy爬取伯樂在線文章

準(zhǔn)備工作:

  • python環(huán)境,我是用Anaconda
  • Scrapy環(huán)境,上一篇文章提到過
  • MySQL,我們準(zhǔn)備將爬取的數(shù)據(jù)保存到MySQL數(shù)據(jù)庫中

創(chuàng)建項目

首先通過scrapy命令創(chuàng)建項目

爬取數(shù)據(jù)整體邏輯

分析一下整個流程,可以分為兩個部分。一,分析列表頁面結(jié)構(gòu),獲取每一篇文章的鏈接和圖片地址以及下一個列表頁地址。二,進(jìn)入文章單頁獲取想要的內(nèi)容數(shù)據(jù)。因此代碼如果都寫在一起顯得非常臃腫,難以閱讀。因此可以在parse函數(shù)處理第一部分邏輯,然后通過Request函數(shù)發(fā)送請求進(jìn)行文章內(nèi)容頁的處理。

    def parse(self, response):
        """
            1獲取文章列表頁的url并交給scrapy下載后進(jìn)行解析
            2獲取下一頁url,交給scrapy下載,下載完成后交給parse
        """
        #解析列表中所有文章url,并交給scrapy
        post_nodes = response.css("#archive .floated-thumb .post-thumb a")
        for post_node in post_nodes:
            image_url = post_node.css("img::attr(src)").extract_first()
            post_url = post_node.css("::attr(href)").extract_first()
            yield Request(url=parse.urljoin(response.url,post_url),meta={"front_image_url":image_url},callback=self.parse_detail)

        #提取下一頁并交給scrapy
        next_urls = response.css(".next.page-numbers::attr(href)").extract_first("")
        if next_urls:
            yield Request(url=parse.urljoin(response.url, next_urls), callback=self.parse)

分析爬取頁面內(nèi)容

本次爬取的內(nèi)容為伯樂在線的文章,我們采取css方式來獲取想要爬取的內(nèi)容,具體css的使用方法我們在上一篇文章提到過,可以參看。

title = response.css('div.entry-header h1::text').extract_first()
create_data = response.css('p.entry-meta-hide-on-mobile::text').extract_first().strip().replace("·","").strip()
praise_nums = response.css('span.vote-post-up h10::text').extract_first()
fav_nums = response.css(".bookmark-btn::text").extract_first()
comment_nums = response.css("a[href='#article-comment'] span::text").extract_first()
tag_list = response.css('p.entry-meta-hide-on-mobile a::text').extract()

文章圖片的獲取

我們可以發(fā)現(xiàn)文章的圖片只是在列表頁里面存在,如果是文章正文中,可能就不會出現(xiàn),因此我們在處理文章鏈接的時候要同時處理文章的圖片。這里用到了Request的一個變量meta,傳遞的內(nèi)容為一個字典。meta={"front_image_url":image_url}

Items

我們數(shù)據(jù)爬取的主要目的是從非結(jié)構(gòu)的數(shù)據(jù)源轉(zhuǎn)化為結(jié)構(gòu)化的數(shù)據(jù)。但是提取數(shù)據(jù)之后,怎么將數(shù)據(jù)進(jìn)行返回呢?數(shù)據(jù)以什么形式返回呢?這時候發(fā)現(xiàn)數(shù)據(jù)缺少了結(jié)構(gòu)化的定義,為了將數(shù)據(jù)進(jìn)行定義,方便格式化和處理,就用到了Item類。此時我們爬取的數(shù)據(jù)可以通過Item進(jìn)行實(shí)例化。Scrapy發(fā)現(xiàn)yield的是一個Item類后,會將我們的Item路由到pipliens中,方便數(shù)據(jù)處理和保存。

class ArticleItem(scrapy.Item):
    title = scrapy.Field()
    create_date = scrapy.Field()
    url = scrapy.Field()
    url_object_id= scrapy.Field()
    front_image_url = scrapy.Field()
    front_image_path = scrapy.Field()
    praise_nums = scrapy.Field()
    comment_nums = scrapy.Field()
    fav_nums = scrapy.Field()
    tags = scrapy.Field()
    content = scrapy.Field()

scrapy圖片自動下載機(jī)制

scrapy提供了一個圖片下載機(jī)制,只需要在settings.py文件夾下的ITEM_PIPELINES增加一句配置
'scrapy.pipelines.images.ImagesPipeline':1,,意思是用scrapy提供的pipline中的images里面的ImagesPipeline。具體路徑如下

image.png

我們可以看到scrapy給我們提供了兩個已經(jīng)完成的pipeline,一個是圖片的一個是媒體的。后面的數(shù)字1代表進(jìn)入pipeline的優(yōu)先級,越小代表優(yōu)先級越高,在多個pipeline同時存在是應(yīng)該注意。
但是還有一個問題,pipeline怎么知道圖片的地址呢?item中的字段那么多,又有哪一個該被傳給pipeline呢?這個還需要在setting文件中配置

IMAGES_URLS_FIELD = "front_image_url"
project_dir = os.path.abspath(os.path.dirname(__file__))
IMAGES_STORE = os.path.join(project_dir,'images')

這樣運(yùn)行的時候會報一個錯:raise ValueError('Missing scheme in request url %s' % self._url),這是因為pipline將IMAGES_URLS_FIELD = "front_image_url"按數(shù)組處理,但是我們item中的圖片地址是一個值,而不是一個數(shù)組。我們可以將item中的值賦值的時候做一下修改:
article_item['front_image_url'] = [front_image_url],在front_image_url上加了一個[],使其可迭代

獲取圖片保存路徑

我們?nèi)绾伟驯镜貓D片地址與文章關(guān)聯(lián)起來呢?比如item中一個字段是圖片的本地地址,我們應(yīng)該怎么做呢?
解決方法就是自己定義一個pipeline,繼承圖片下載的pipeline

class ArticleImagePipeline(ImagesPipeline):
    def item_completed(self, results, item, info):
        if "front_image_url" in item:
            for ok, value in results:
                image_file_path = value["path"]
            item["front_image_path"] = image_file_path

        return item

以JSON形式保存

class JsonExporterPipleline(object):
    #調(diào)用scrapy提供的json export導(dǎo)出json文件
    def __init__(self):
        self.file = open('articleexport.json', 'wb')
        self.exporter = JsonItemExporter(self.file, encoding="utf-8", ensure_ascii=False)
        self.exporter.start_exporting()

    def close_spider(self, spider):
        self.exporter.finish_exporting()
        self.file.close()

    def process_item(self, item, spider):
        self.exporter.export_item(item)
        return item

定義MySQL表結(jié)構(gòu)

DROP TABLE IF EXISTS `jobbole_article`;
CREATE TABLE `jobbole_article`  (
  `title` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `create_date` date NULL DEFAULT NULL,
  `url` varchar(300) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `url_object_id` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `front_image_url` varchar(300) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `front_image_path` varchar(300) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `comment_nums` int(11) NOT NULL DEFAULT 0,
  `fav_nums` int(11) NOT NULL DEFAULT 0,
  `praise_nums` int(11) NOT NULL DEFAULT 0,
  `tags` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `content` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL,
  PRIMARY KEY (`url_object_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

SET FOREIGN_KEY_CHECKS = 1;

安裝MySQL庫

使用pip install mysqlclient可以安裝mysqlclient,如果是python2那么可以安裝mysqldb,是一樣的功能,API都相同。Linux下安裝可能報錯,如果是ubuntu需要執(zhí)行sudo apt-get install libmysqlclient-dev,如果是centos可以執(zhí)行sudo yum install python-devel mysql-devel

定義pipeline保存數(shù)據(jù)到MySQL

class MysqlPipeline(object):
    def __init__(self):
        self.conn = MySQLdb.connect('127.0.0.1', 'root', 'root', 'article_spider', charset='utf8', use_unicode=True)
        self.cursor = self.conn.cursor()
    def process_item(self, item, spider):
        insert_sql = 'INSERT INTO jobbole_article (`title`, `create_date`, `url`, `url_object_id`, `content`, `front_image_path`, `comment_nums`, `fav_nums`, `praise_nums`, `tags`) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)'
        self.cursor.execute(insert_sql, (item['title'], item['create_date'], item['url'], item['url_object_id'], item['content'], item["front_image_path"], item['comment_nums'], item['fav_nums'], item['praise_nums'], item['tags']))
        self.conn.commit()

定義pipeline異步保存數(shù)據(jù)到MySQL

  • 上述的插入方法是同步插入,意味著這句話執(zhí)行不結(jié)束下面的工作沒法去做
  • 另外一個原因是spider的解析速度遠(yuǎn)大于插入數(shù)據(jù)的速度,這樣到后期爬取的item越來越多,插入速度跟不上解析速度,就會造成堵塞
  • 另外異步插入可以根據(jù)不同的item定制插入語句,而不用寫多個pipeline
class MysqlTwistedPipline(object):
        def __init__(self,dbpool):
            self.dbpool = dbpool
        @classmethod
        def from_settings(cls,settings):
           dbparms = dict(
                host = settings["MYSQL_HOST"],
                db = settings["MYSQL_DBNAME"],
                user = settings["MYSQL_USER"],
                passwd = settings["MYSQL_PASSWORD"],
                charset = 'utf8',
                cursorclass = MySQLdb.cursors.DictCursor,
                use_unicode = True
            )
           dbpool = adbapi.ConnectionPool("MySQLdb",**dbparms)
           return cls(dbpool)
        def process_item(self,item,spider):
            #使用twisted將MYSQL插入編程異步執(zhí)行
            query = self.dbpool.runInteraction(self.do_insert,item,)
            query.addErrback(self.handle_error,item,spider)#處理異常
        def handle_error(self,failure,item,spider):
            print(failure) #處理異步插入的異常
        def do_insert(self,cursor,item):
            # 執(zhí)行具體的插入
            # 根據(jù)不同的item 構(gòu)建不同的sql語句并插入到mysql中
            insert_sql, params = item.get_insert_sql()
            cursor.execute(insert_sql, params)

使用itemloader

既然已經(jīng)有了item,那為什么要使用itemloader呢?我們可以看到不管是xpath,或者css,都是需要extract,然后可能還需要正則化處理,這樣以后的維護(hù)工作會變得很困難。

# 通過item loader加載item
        front_image_url = response.meta.get("front_image_url", "")  # 文章封面圖

        item_loader = ArticleItemLoader(item=JobBoleArticleItem(), response=response)

        # 通過css選擇器將后面的指定規(guī)則進(jìn)行解析。
        item_loader.add_css("title", ".entry-header h1::text")
        item_loader.add_value("url", response.url)
        item_loader.add_value("url_object_id", get_md5(response.url))
        item_loader.add_css("create_date", "p.entry-meta-hide-on-mobile::text")
        item_loader.add_value("front_image_url", [front_image_url])
        item_loader.add_css("praise_nums", ".vote-post-up h10::text")
        item_loader.add_css("comment_nums", "a[href='#article-comment'] span::text")
        item_loader.add_css("fav_nums", ".bookmark-btn::text")
        item_loader.add_css("tags", "p.entry-meta-hide-on-mobile a::text")
        item_loader.add_css("content", "div.entry")

        # 調(diào)用這個方法來對規(guī)則進(jìn)行解析生成item對象
        article_item = item_loader.load_item()

        # 已經(jīng)填充好了值調(diào)用yield傳輸至pipeline
        yield article_item

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,990評論 2 374

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

  • 一、總體思路: 1、下載start_urls,交給parse方法處理,文章列表頁start_urls = ['ht...
    阿軻666閱讀 864評論 0 2
  • 有半個月沒有更新了,最近確實(shí)有點(diǎn)忙。先是華為的比賽,接著實(shí)驗室又有項目,然后又學(xué)習(xí)了一些新的知識,所以沒有更新...
    qiye閱讀 9,964評論 35 87
  • 你永遠(yuǎn)無法想象一個感情容易泛濫的人每時每刻的心情和想法。因為可能連其本人都說不清道不明。 從開始到成長,就像孕育了...
    朽木333閱讀 540評論 0 1