爬蟲學習之基於Scrapy的網絡爬蟲


概述


在上一篇文章《爬蟲學習之一個簡單的網絡爬蟲》中我們對爬蟲的概念有了一個初步的認識,並且通過Python的一些第三方庫很方便的提取了我們想要的內容,但是通常面對工作當作復雜的需求,如果都按照那樣的方式來處理效率非常的低,這通常需要你自己去定義並實現很多非常基礎的爬蟲框架上的功能,或者需要組合很多Python第三方庫來做。不過不用擔心,Python中有很多非常優秀的爬蟲框架,比如我們接下來要學習到的Scrapy。Scrapy官方有很經典的入門文檔說明,這一篇僅僅是通過一個簡單的實例來了解Scrapy這個庫是如何來進行網絡內容提取的,更深入的學習請閱讀Scrapy官方文檔

建立目標


同樣在做任何事情之前都需要明確目標,那這次我們的目標是爬取一些技術性的文章並存儲到數據庫中。這就需要有目標網址和數據庫結構,數據庫我們選擇使用MySql,目標網站我們找了一個叫腳本之家的內容站。我們這里首先准備好一張用於存儲文章的表結構:

CREATE TABLE `articles` (
  `id` mediumint(8) AUTO_INCREMENT NOT NULL,
  `title` varchar(255) DEFAULT NULL,
  `content` longtext,
  `add_date` int(11) DEFAULT 0,
  `hits` int(11) DEFAULT '0',
  `origin` varchar(500) DEFAULT '',
  `tags` varchar(45) DEFAULT '',
  PRIMARY KEY (`id`),
  KEY `add_date` (`add_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

分析目標結構

這里我們首先需要爬取得入口是“網絡編程”這個節點,主入口網址為(http://www.jb51.net/list/index_1.htm) 打開這個網站我們通過Chrome或者其他瀏覽器的查看元素來分析當前頁面的HTML語義結構,如下圖所示:

從圖中紅色框線的部分可以看出,這里是我們需要在“網絡編程”這個節點下需要提取的所有文章的主分類入口,通過這些入口可以進去到不同文章分類的列表中。所以根據初步結構分析,我們得出本次爬蟲的爬取路線為:

從主入口進去 -> 提取當前入口中的所有分類 -> 通過分類入口進入到分類列表 -> 通過列表進入到文章頁

分類入口確定了接下來看看我們的分類列表,隨意點開一個分類入口,打開列表如下圖所示:

這里我框出了兩個主要部分,第一個是文章的標題,第二個是分頁,文章對應的URL就是我們接下來需要爬取文章內容的入口,這里需要注意的是分頁的處理,通過分頁的最后一頁我們可以知道當前這類列表共有多少頁文章。結合以上分析我們基本確定了本次爬蟲的各個路線入口,接下來我們就開始通過程序來實現本次的目標。

實現爬蟲


在實現爬蟲之前我們通過一張圖來對Scrapy有個基本的認識,為了保持本章內容的簡潔性,我們這里暫時不會討論Item Pipeline部分,Scrapy架構圖如下所示(圖片來自網絡):

從圖中可以很清晰的看到Scrapy所包含的幾大塊,下面我們通過代碼來演示我們所用到的基礎功能部分。
主要依賴第三方庫:

web.py web框架,這里只用到了database部分,將來會用來進行內容展示
scrapy 爬蟲框架,這里只用到了最基本的內容提取

這里還會用到一些xpath相關知識,請自行Google了解xpath語法

# -*- coding:utf-8 -*-
'''by sudo rm -rf  http://imchenkun.com'''
import scrapy
from scrapy.http import Request
import web
import time

db = web.database(dbn='mysql', host='127.0.0.1', db='imchenkun', user='root', pw='root')

# 允許的站點域
allow_domain = "jb51.net"

base_url = "http://www.jb51.net"

# 列表頁
list_url = "http://www.jb51.net/list/list_%d_%d.htm"

# 列表分頁
list_page = 1

# 文章頁
crawl_url = "http://www.jb51.net/article/%d.htm"


class JB51Spider(scrapy.Spider):
    name = "jb51"
    start_urls = [
        "http://www.jb51.net/list/index_1.htm"
    ]

    cate_list = []

    def parse(self, response):
        cate_id = response.selector.xpath('//div[@class="index_bor clearfix"]/div[@class="index_con"]/span/a/@href').re('(\\\\d+)')[::2]
        for id in cate_id:
            cate_url = list_url % (int(id), 1)
            yield Request(cate_url, callback=self.parse_page)

    def parse_page(self, response):
        _params = response.selector.xpath('//div[@class="dxypage clearfix"]/a[last()]/@href').re('(\\\\d+)')
        cate_id = int(_params[0]) # 分類編號
        count = int(_params[1]) # 總頁數

        article_urls = response.selector.xpath('//div[@class="artlist clearfix"]/dl/dt/a/@href').extract()
        # 處理第一頁
        for article_url in article_urls:
            yield Request(base_url + article_url, callback=self.parse_article)

        # 處理其他頁
        for page in range(1, count):
            url = (list_url % (cate_id, page + 1))
            yield Request(url, callback=self.parse_list)

    def parse_list(self, response):
        """解析文章列表"""
        article_urls = response.selector.xpath('//div[@class="artlist clearfix"]/dl/dt/a/@href').extract()
        for article_url in article_urls:
            yield Request(base_url + article_url, callback=self.parse_article)

    def parse_article(self, response):
        """解析文章內容"""
        title = response.selector.xpath('//div[@class="title"]/h1/text()').extract()[0]
        content = response.selector.xpath('//div[@id="content"]').extract()[0]
        tags = ','.join(response.selector.xpath('//div[@class="tags mt10"]/a/text()').extract())
        
        results = db.query('select count(0) as total from articles where origin=$origin', vars = { 'origin': response.url })
        if results[0].total <= 0:
            db.insert('articles',
                      title=title,
                      origin=response.url,
                      content=content,
                      add_date=int(time.time()),
                      hits=0,
                      tags=tags
            )

安裝Scrapy后以上代碼通過以下命令執行:

scrapy runspider jb51_spider.py

本次運行后的效果在數據庫中可以見如下圖所示:

Github地址

總結


本篇文章我們主要了解了基本的Scrapy Spider部分,而且通過對目標網站的結構分析使用xpath進行內容的提取,以及分頁的處理。這里我們的目的是建立一種寫爬蟲的思路,而不在於怎么使用工具來爬數據。首先確定目標,然后分析目標,再借助現有工具進行內容提取,提取內容的過程中會遇到各種問題,這個時候我們再來逐個解決這些問題,直到我們的爬蟲能夠無障礙的運行。接下來我會使用Scrapy更多的功能將繼續探索Item的定義,Pipeline的實現以及如何使用代理。

特別申明:本文所提到的腳本之家網站只是拿來進行爬蟲的技術交流學習,讀者涉及到的所有侵權問題都與本人無關,也希望大家在學習實戰的過程中不要大量的爬取內容對服務器造成負擔

本文首發在sudo rm -rf 轉載請注明原作者


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM