《爬蟲筆記》初學爬蟲:入門進階(2)

目標: 

這裏是在入門進階(1)的代碼基礎,並需要滿足下面幾個條件

1. 爬取正確的數據

    (1) 對爬取的數據進行格式轉換

    (2) 根據自定義規則,拆分標題、章節數

2. 爬取的數據存入數據庫

    (1) 新建數據庫

    (2) settings.py中配置數據連接信息

    (3) 引入已經寫好的mysql.py

    (4) 判斷數據庫是否存在,避免爬取的數據重複

    (5) 新數據存庫

一、使用mysql

1. 引入 mysql.py

我這裏直接引用別人寫好的 mysql.py 數據庫連接工具,也可以自己手寫

 

"""
數據庫工具類
# """
import pymysql
import traceback
from DBUtils.PooledDB import PooledDB
from scrapy.utils.project import get_project_settings


class MysqlUtil(object):

    # 獲取setting文件中的配置
    settings = get_project_settings()

    config = {
        'host': settings.get('MYSQL_HOST'),
        'port': settings.get('MYSQL_PORT'),
        'database': settings.get('MYSQL_DATABASE'),
        'user': settings.get('MYSQL_USER'),
        'password': settings.get('MYSQL_PASSWORD'),
        'charset': settings.get('MYSQL_CHARSET')
    }

    """
    MYSQL數據庫對象,負責產生數據庫連接 , 此類中的連接採用連接池實現獲取連接對象:conn = Mysql.getConn()
            釋放連接對象;conn.close()或del conn
    """
    # 連接池對象
    __pool = None

    def __init__(self):
        # 數據庫構造函數,從連接池中取出連接,並生成操作遊標
        self._conn = MysqlUtil.get_conn()
        self._cursor = self._conn.cursor()

    # 獲取鏈接
    @staticmethod
    def get_conn():
        """
        @summary: 靜態方法,從連接池中取出連接
        @return MySQLdb.connection
        """
        if MysqlUtil.__pool is None:
            __pool = PooledDB(creator=pymysql, mincached=1, maxcached=100, host=MysqlUtil.config['host'], port=MysqlUtil.config['port'], user=MysqlUtil.config['user'], passwd=MysqlUtil.config['password'], db=MysqlUtil.config['database'], charset='utf8')
        return __pool.connection()

    # 查詢所有數據
    def get_all(self, sql, param=None):
        """
        @summary: 執行查詢,並取出所有結果集
        @param sql:查詢SQL,如果有查詢條件,請只指定條件列表,並將條件值使用參數[param]傳遞進來
        @param param: 可選參數,條件列表值(元組/列表)
        @return: result list(字典對象)/boolean 查詢到的結果集
        """
        try:
            if param is None:
                count = self._cursor.execute(sql)
            else:
                count = self._cursor.execute(sql, param)
            if count > 0:
                result = self._cursor.fetchall()
            else:
                result = False
            return result
        except Exception as e:
            traceback.print_exc(e)

    # 查詢某一個數據
    def get_one(self, sql, param=None):
        """
        @summary: 執行查詢,並取出第一條
        @param sql:查詢SQL,如果有查詢條件,請只指定條件列表,並將條件值使用參數[param]傳遞進來
        @param param: 可選參數,條件列表值(元組/列表)
        @return: result list/boolean 查詢到的結果集
        """
        try:
            if param is None:
                count = self._cursor.execute(sql)
            else:
                count = self._cursor.execute(sql, param)
            if count > 0:
                result = self._cursor.fetchone()
            else:
                result = False
            return result
        except Exception as e:
            traceback.print_exc(e)

    # 查詢數量
    def get_count(self, sql, param=None):
        """
        @summary: 執行查詢,返回結果數
        @param sql:查詢SQL,如果有查詢條件,請只指定條件列表,並將條件值使用參數[param]傳遞進來
        @param param: 可選參數,條件列表值(元組/列表)
        @return: result list/boolean 查詢到的結果集
        """
        try:
            if param is None:
                count = self._cursor.execute(sql)
            else:
                count = self._cursor.execute(sql, param)
            return count
        except Exception as e:
            traceback.print_exc(e)

    # 查詢部分
    def get_many(self, sql, num, param=None):
        """
        @summary: 執行查詢,並取出num條結果
        @param sql:查詢SQL,如果有查詢條件,請只指定條件列表,並將條件值使用參數[param]傳遞進來
        @param num:取得的結果條數
        @param param: 可選參數,條件列表值(元組/列表)
        @return: result list/boolean 查詢到的結果集
        """
        try:
            if param is None:
                count = self._cursor.execute(sql)
            else:
                count = self._cursor.execute(sql, param)
            if count > 0:
                result = self._cursor.fetchmany(num)
            else:
                result = False
            return result
        except Exception as e:
            traceback.print_exc(e)

    # 插入一條數據
    def insert_one(self, sql, value):
        """
        @summary: 向數據表插入一條記錄
        @param sql:要插入的SQL格式
        @param value:要插入的記錄數據tuple/list
        @return: insertId 受影響的行數
        """
        try:
            row_count = self._cursor.execute(sql, value)
            self.end("commit")
            return row_count
        except Exception as e:
            traceback.print_exc(e)
            self.end("rollback")

    # 插入多條數據
    def insert_many(self, sql, values):
        """
        @summary: 向數據表插入多條記錄
        @param sql:要插入的SQL格式
        @param values:要插入的記錄數據tuple(tuple)/list[list]
        @return: count 受影響的行數
        """
        try:
            row_count = self._cursor.executemany(sql, values)
            self.end("commit")
            return row_count
        except Exception as e:
            traceback.print_exc(e)
            self.end("rollback")

    # def __get_insert_id(self):
    #     """
    #     獲取當前連接最後一次插入操作生成的id,如果沒有則爲0
    #     """
    #     self._cursor.execute("SELECT @@IDENTITY AS id")
    #     result = self._cursor.fetchall()
    #     return result[0]['id']

    # 執行sql
    def __query(self, sql, param=None):
        try:
            if param is None:
                count = self._cursor.execute(sql)
            else:
                count = self._cursor.execute(sql, param)
            self.end("commit")
            return count
        except Exception as e:
            traceback.print_exc(e)
            self.end("rollback")

    # 更新
    def update(self, sql, param=None):
        """
        @summary: 更新數據表記錄
        @param sql: SQL格式及條件,使用(%s,%s)
        @param param: 要更新的  值 tuple/list
        @return: count 受影響的行數
        """
        return self.__query(sql, param)

    # 刪除
    def delete(self, sql, param=None):
        """
        @summary: 刪除數據表記錄
        @param sql: SQL格式及條件,使用(%s,%s)
        @param param: 要刪除的條件 值 tuple/list
        @return: count 受影響的行數
        """
        return self.__query(sql, param)

    def begin(self):
        """
        @summary: 開啓事務
        """
        self._conn.autocommit(0)

    def end(self, option='commit'):
        """
        @summary: 結束事務
        """
        if option == 'commit':
            self._conn.commit()
        else:
            self._conn.rollback()

    def dispose(self, is_end=1):
        """
        @summary: 釋放連接池資源
        """
        if is_end == 1:
            self.end('commit')
        else:
            self.end('rollback')
        self._cursor.close()
        self._conn.close()

2. 新建數據庫

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for fiction
-- ----------------------------
DROP TABLE IF EXISTS `fiction`;
CREATE TABLE `fiction`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵id',
  `chapter` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '小說章節數',
  `title` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '小說標題',
  `content` longtext CHARACTER SET utf8 COLLATE utf8_general_ci COMMENT '小說內容',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 35 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

3. 配置數據庫連接

在settings.py 中添加下面的數據庫連接配置

# MYSQL SETTINGS
MYSQL_HOST = '127.0.0.1'
MYSQL_USER = 'root'
MYSQL_PASSWORD = '123456'
MYSQL_DATABASE = 'mypython'
MYSQL_PORT = 3306

4. 對代碼進行編寫

根據頁面分析,小說頁面的末端存在【下一頁】,根據爬蟲自動觸發 不斷的循環爬取小說

// 這裏爬取了 小說的:章節數、標題、內容

由於 章節數、標題 在同一個div中,所以需要自定義規則進行拆分

先根據章節數,從數據庫查詢是否存在該小說,如果不存在,就對提取出來的數據進行存庫;如果存在,則不做操作

import scrapy
# 引入mysql工具類
from ..mysql import MysqlUtil

# 運行scrapy項目的命令: scrapy crawl test
class myclass(scrapy.Spider):

    # 爬蟲名
    name ='test'
    
	# 爬取的鏈接
    start_urls = ["http://book.zongheng.com/chapter/885037/58155562.html"]

    def __init__(self):
        self.pool = MysqlUtil()

    def parse(self, response):
	
        # 標題章節div(包括章節數、本章名稱)
        title_1 = response.xpath("//div[@class='title_txtbox']/text()").extract()[0]

        # 1.提取標題,章節數以外的文字
        if "章" in str(title_1):
            title = str(title_1)[str(title_1).index("章") + 1:]
        else:
            title = str(title_1)

        # 2.提取章節數,根據"章"字對標題進行截取
        if "章" in str(title_1):
            chapter = str(title_1)[0:str(title_1).index("章") + 1]
        else:
            chapter = "-"

        # 3.內容 content
        content_1 = response.xpath("//div[@class='content']//text()").extract()
        # 數組轉字符串: String = "".join(arrary)
        # 字符串清除空格: String.strip()
        # 數組轉字符串,並清空內容裏的空格
        content = "".join(content_1).strip()

        # 數據入庫
        selectSql = """select * from fiction where chapter = %s """
        params1 = (chapter)
        returnData = self.pool.get_one(selectSql, params1)
        if returnData == False:
            insertSql = """insert into fiction(chapter, title, content)values(%s, %s, %s)"""
            params = (chapter, title, content)
            self.pool.insert_one(insertSql, params)

        # 下一頁
        link = response.xpath("//div[@class='chap_btnbox']//a[@class='nextchapter']/@href").extract()[0]
        print(link)

        # 重複循環爬取
        yield scrapy.Request(link, callback=self.parse, dont_filter=True)

4. 啓動scrapy爬蟲項目

# scrapy crawl 爬蟲名
scrapy crawl test

慢慢等着,然後數據庫就開始進數據了~~~ 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章