scrapy入門學習初步探索之數據保存

在上一篇文章中,談及瞭如何使用scrapy取獲取網頁數據,可參考Scrapy入門學習之初步探索。在此繼續談一下數據保存和一些settings.py的配置。html

文中會繼續上文的豆瓣讀書小項目,並新增一個爬取簡書全部用戶信息的爬蟲小項目,文章力求可以講清學習時所遇到的坑,因此略顯冗長。如若您沒有耐心,就別往下翻了O(∩_∩)O~python

環境申明:
Python3+Pycharm2017
所需第三方包:
scrapy,pymysql
數據庫:
MySQL5.7mysql



使用item-pipeline保存數據

學習爬蟲主要是爲了收集一些數據用於分析,而用的多的數據格式無非就是json,csv或是經過數據庫MySQL,mongodb等。ajax

在此,系統的記錄相應的數據格式處理方式。sql

OK,這裏你須要作的有:mongodb

  1. 在items.py中定義你須要的數據
  2. 在pipelines.py中定義你處理數據的方式
  3. 在settings.py中註冊定義的pipeline

首先scrapy自帶了相應模塊用於處理數據,在item export中數據庫

  • ‘CsvItemExporter’,
  • ‘JsonItemExporter’

scrapy文檔使用 Item Exporter
建議直接閱讀源碼django

保存爲csv數據

  • 自定義保存

在Pipelines.py中定義item數據的處理方式,以下代碼,定義了CsvPipeline類,並在settings.py中的ITEM_PIPELINES註冊便可。json

ITEM_PIPELINES = {
#’JianShu.pipelines.JianshuPipeline’: 300 ,
’ JianShu.pipelines.JsonEncodingPipeline’:1 ,#這裏1是優先級
}promise

注意

啓用一個 Item Pipeline 組件,
必須將它的類添加到 ITEM_PIPELINES 配置,分配給每一個類的整型值,肯定了他們運行的順序,item 按數字從低到高的順序,經過 pipeline,一般將這些數字定義在 0-1000 範圍內。

class CsvPipeline(object):
    def __init__(self):
        self.csvf=open('db_Top250.csv','a+',encoding='utf-8',newline='')
        self.writer=csv.writer(self.csvf)#這裏能夠加入delimiter=','分隔符參數,默認爲逗號
        self.writer.writerow(['書名','做者','國家','薦語','出版時間','出版社','評分','Star','參與短評人次','力薦','推薦','還行','較差','爛'])
        self.csvf.close()

    def process_item(self,item,spider):
        with open('db_Top250.csv','a+',encoding='utf-8',newline='')as f:
            writer=csv.writer(f)
            writer.writerow([item['title'],item['author'],item['country'],item['note'],item['publish_date'],item['press'],item['Score'],item['Star'],item['People_nums'],item['s5'],item['s4'],item['s3'],item['s2'],item['s1']])

        return item

注意事項:

  1. 打開文件時有個newline=’ ‘參數,避免寫入數據後會空一行
  2. encoding=’utf-8’,碰到了excel中打開csv中文沒法正常顯示的狀況,如圖。目前解決方法有:

    用記事本、sublime或pycharm打開,能夠正常顯示。再以ANSI或utf8-bom編碼保存,再次用excel打開正常顯示


  • 使用item export中的CsvItemExporter

同樣要在settings.py中註冊

導入from scrapy.contrib.exporter import CsvItemExporter

一樣是在pipelines.py中

class CSVPipeline(object):

  def __init__(self):
    self.files = {}

  def from_crawler(cls, crawler):
    pipeline = cls()
    crawler.signals.connect(pipeline.spider_opened, signals.spider_opened)
    crawler.signals.connect(pipeline.spider_closed, signals.spider_closed)
    return pipeline

  def spider_opened(self, spider):
    file = open('%s_items.csv' % spider.name, 'w+b')
    self.files[spider] = file
    self.exporter = CsvItemExporter(file)
    #self.exporter.fields_to_export = [list with Names of fields to export - order is important]
    self.exporter.fields_to_export = ['title','author','country','note','publish_date','press','Score','Star','People_nums']
    #這裏是序列化 item fields,排序很重要

    self.exporter.start_exporting()

  def spider_closed(self, spider):
    self.exporter.finish_exporting()
    file = self.files.pop(spider)
    file.close()

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

關於csv文件中空行的問題,我這裏查了好久都沒解決,file = open('%s_items.csv' % spider.name, 'w+b')添加newline會報錯。總之仍是我太菜啦(^_^。若是你有解決辦法,不妨在文末留言,thx。

更多內容能夠閱讀

【1】scrapy文檔

【2】Python Scrapy: How to get CSVItemExporter to write columns in a specific order


這裏寫圖片描述
程序運行如圖
這裏寫圖片描述
自定義生成的csv
這裏寫圖片描述
item export中的CsvItemExporter所生成的csv
這裏寫圖片描述
這是不指定field順序(測試時s5~s1沒有寫入item)

爬取簡書用戶信息

這裏咱們再也不爬取豆瓣Top250了,由於我寫文章時可能頻繁爬取的緣故,老是封我。這裏改用爬取簡書用戶信息爲例,入口josonLe的關注頁面,這就是我,你們能夠關注一波,往後可能會寫一些文在這裏。

這裏咱們選取用戶的關注列表爲採集信息入口(由於用戶的關注數每每大於粉絲數,畢竟又不是誰都是大V)

如圖,簡書用戶信息是經過ajax動態加載的,下拉滑動條就能在開發者工具(F12,上一篇文中講到了,上面有連接)中抓到包
這裏寫圖片描述
看箭頭所指
這裏寫圖片描述
咱們能夠得出關注者的請求url爲’https ://www.jianshu.com/users/1fdc2f8e1b37/following?page=2’,其中只有page參數發生了變化。再看箭頭所指的publication頁面count參數爲10,猜想每頁10條數據。因此page就是關注者數目除以10向上取整。

而後是獲取用戶信息的思路,如箭頭所示。進入我的主頁後,先獲取關注頁面,再獲取關注列表中全部用戶主頁,重複便可。過程當中獲取相應信息(name、關注、粉絲、文章、字數、收穫喜歡、我的簡介。專題、文章等經過json數據返回,感受獲取也沒用,就沒有寫)
這裏寫圖片描述

這裏咱們先用剛剛所學的csv格式處理數據
先看一下成果圖,大概不到7小時,一共爬取了61173條數據
98hp36.png

直接上代碼吧,

spider
# -*- coding: utf-8 -*-
import scrapy
import math
from scrapy.http import Request
from JianShu.items import JianshuItem # 引入items重定義的item

class JianshuSpider(scrapy.Spider):
    name = 'jianshu'
    allowed_domains = ['www.jianshu.com']
    start_urls = ['https://www.jianshu.com/users/1fdc2f8e1b37/following']
    #我的頁面
    up_urls='https://www.jianshu.com/users/{id}/following'
    #關注頁面
    follow_urls='https://www.jianshu.com/users/{id}/following?page='
    id_set=set() #用於用戶去重


    def parse(self, response):
        item=JianshuItem()

        try:
            item['name'] = response.xpath('//div[@class="main-top"]/div[@class="title"]/a/text()').extract_first('')
            up_id = response.xpath('//div[@class="main-top"]/div[@class="title"]/a/@href').extract_first('').split('/')[-1]
            self.id_set.add(up_id)
            item['id']=up_id
            print('開始解析{}'.format(item['name']))

            selector = response.xpath('//div[@class="main-top"]/div[@class="info"]/ul/li')
            # 關注的人
            num=int(selector[0].xpath('./div/a/p/text()').extract_first(''))
            item['following'] = num
            pages = math.ceil(num/10)#翻頁pages,向上取整
            # 粉絲
            item['follower'] = int(selector[1].xpath('./div/a/p/text()').extract_first(''))
            item['articles'] = int(selector[2].xpath('./div/a/p/text()').extract_first(''))  # 文章
            item['words'] = int(selector[3].xpath('./div/p/text()').extract_first(''))  # 字數
            item['likes'] = int(selector[4].xpath('./div/p/text()').extract_first(''))  # 收穫喜歡
            # 做者簡介
            item['introduction'] = response.xpath('//div[@class="description"]/div/text()').extract_first('')

        except:
            pass
        else:
            yield item
            for i in range(1, int(pages) + 1):
                up_url = self.follow_urls.format(id=up_id) + str(pages)
                yield Request(url=up_url, callback=self.userlist_parse)
        pass
    def userlist_parse(self,response):
        urls=response.xpath('//div[@class="info"]/a[@class="name"]/@href').extract()
        #列表推導式,主要執行兩步,一是獲取每一個關注者的url,而是url去重
        url_list=[[self.up_urls.format(id=url_id.split('/')[-1]),self.id_set.add(url_id.split('/')[-1])] for url_id in urls if url_id not in self.id_set]
        # self.id_set.add(id.split('/')[-1]) for id in urls
        for url in url_list:
            yield Request(url=url[0], callback=self.parse)
items.py

如同django中數據定義,經過scrapy.Field()定義數據

import scrapy
class JianshuItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() name=scrapy.Field() id=scrapy.Field() following=scrapy.Field() follower=scrapy.Field() likes=scrapy.Field() articles=scrapy.Field() words=scrapy.Field() introduction=scrapy.Field()
pipelines.py的編寫

能夠直接套用上面的模板,稍做修改便可

import csv
class CSVPipeline(object):
    def __init__(self):
        self.csvfile=open('JianShu_author_messages.csv','a+',encoding='utf-8',newline='')
        self.writer=csv.writer(self.csvfile)
        self.writer.writerow(('名','id','關注','粉絲','得到喜歡','文章','字數','我的簡介'))
        self.csvfile.close()

    def process_item(self,item,spider):
        with open('JianShu_author_messages.csv','a+',encoding='utf-8',newline='')as f:
            writer=csv.writer(f)
            writer.writerow((item['name'],item['id'],item['following'],item['follower'],item['likes'],item['articles'],item['words'],item['introduction']))

        return item
反爬處理及settings配置

重寫下載中間件middlewares.py,引入隨機UA。具體內容可參考我上一篇文,頂部有連接。

# Obey robots.txt rules
# 不遵照robots協議
ROBOTSTXT_OBEY = False
# Configure maximum concurrent requests performed by Scrapy (default: 16)
# 增長併發,併發是指同時處理的request的數量,默認爲32。增長多少取決於爬蟲能佔用多少CPU
CONCURRENT_REQUESTS = 100
#下載延遲,是在必定範圍內隨機的
DOWNLOAD_DELAY = 0.2
# Disable cookies (enabled by default)
#禁用cookies,能減小CPU使用率及Scrapy爬蟲在內存中記錄的蹤影,提升性能。
COOKIES_ENABLED = False
#禁止重試,有些網站響應慢,可能會超時,屢次重試會較低爬取效率
RETRY_ENABLED = False
#設置下載超時,一些超時的請求放棄
DOWNLOAD_TIMEOUT = 30
#下載中間件註冊
DOWNLOADER_MIDDLEWARES = {
   # 'JianShu.middlewares.MyCustomDownloaderMiddleware': 200,
    'JianShu.middlewares.RandomUserAgent':100,
}
UserAgentList=[
 ...這裏參考上一篇文章
]
#管道文件相對應數據處理的配置,數字越小優先級越高
ITEM_PIPELINES = {
   # 'JianShu.pipelines.JianshuPipeline': 300,
   # 'JianShu.pipelines.CSVPipeline':200,
    'JianShu.pipelines.JsonEncodingPipeline':100,
}

保存爲json數據

一樣是用上文的簡書做者信息爲列,

  • 自定義保存

在查了一些資料後,發現了一些可能會碰到的問題。如寫入文件編碼的問題等。
這裏導入模塊

import json #這不用多說,py自帶的json數據處理包
import codecs #codecs打開文件能夠避免一些中文編碼問題

#咱們定義JsonPipeline類
class JsonPipeline(object):
    #程序運行開始時,打開文件
    def __init__(self):
        #print('start writing in files...')
        self.file = codecs.open('.json', 'a+', encoding='utf-8')

    def process_item(self, item, spider):
        #print('writing...')
        line = json.dumps(dict(item), ensure_ascii=False) + "\n"
        self.file.write(line)
        return item #注意務必返回item,以供其餘pipeline處理

    def spider_closed(self, spider):
        #print('ok,file is closed...')
        self.file.close()

注意事項:

  • item要返回
  • ensure_ascii=False,這裏不寫成False,寫入文件時會把unicode編碼格式寫入
  • 利用json.dumps()把數據轉化爲json格式時,要先變爲字典
  • 別忘了在settings.py中註冊



  • 使用exporter的JsonItemExporter

from scrapy.exporters import JsonItemExporter
class JsonExporterPipleline(object):
    #調用scrapy提供的json export導出json文件
    def __init__(self):
        self.file = open('jianshu_messsageexport.json', 'wb')
        self.exporter = JsonItemExporter(self.file, encoding="utf-8", ensure_ascii=False)
    # 打開文件,並實例化exporter
        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

成果圖示,6個多小時57334條數據(和網速,內存,cpu有關)
這裏寫圖片描述

保存MySQL數據庫

我也是初步學習python連接數據庫,因此在一番查閱資料後,發現主要有PyMySQL和MySQLdb兩種用於連接MySQL數據庫的第三方包,可是MySQLdb再也不支持python3.5以後的,因此選擇PyMySQL進行學習。

首先是安裝PyMySQL,進入虛擬環境,執行pip insatll PyMySQL,當前版本爲0.8.0(你也能夠經過pycharm安裝,或者不選用虛擬環境,如何使用虛擬環境,使用pycharm安裝可看這裏

經過管理員權限開啓數據庫,net start mysql

編寫代碼前,先設計MySQL的數據表,這裏我使用Navicat for Windows進行操做。這裏與內容無關,很少做介紹。表如圖示
這裏寫圖片描述
這裏咱們經過可視化工具建立了一個名爲jianshu的dbbase,建立了一張名爲author_message的表。

這裏不得不提醒,像name,introduction這種中文文本必定要保存爲utf8編碼(最初是以latin保存的,一直沒法寫入數據庫,淚哭),還有就是每欄的長度必定要合適(最初沒有作異常處理,超過了長度都不知道錯在哪裏)。還有一些問題,折騰了一夜才整出來,無語。。。

這裏咱們先看代碼,不考慮獲取數據速度遠大於存取速度所形成堵塞

#同步使用PyMySQL連接數據庫
class MySQLPipeline(object):
    #初始化鏈接數據庫
    def __init__(self):
    #密碼,用戶名,數據庫改爲你本身的
        self.connect=pymysql.connect(host='127.0.0.1',user='root',password='123456',db='jianshu',charset='utf8',use_unicode=True )
        self.cursor=self.connect.cursor()

    def process_item(self, item, spider):
        sql = 'insert into author_message(name,id,following,follower,likes,articles,words,introduction) values(%s,%s,%s,%s,%s,%s,%s,%s)'
        try:
            # execute執行sql語句
            self.cursor.execute(sql, (item['name'], item['id'], item['following'], item['follower'], item['likes'],item['articles'],item['words'],item['introduction']))
            #向數據庫提交修改,不要漏了
            self.connect.commit()
            print('寫入MySQL成功。。。')
        except Exception as e:
            #self.connect.rollback()#發生錯誤則回滾,能夠不寫
            print('insert error:',e)
    def close_spider(self):
        self.connect.close()

核心是你要知道數據庫的插入語句INSERT INTO TABLE(。。。) VALUES(。。。)

好比說插入第一條信息name:josonle, id:1fdc2f8e1b37,following:11。。。
實際上就是使用execute調用命令行執行insert into author_message(name,id,following...) values('josonle','1fdc2f8e1b37','11'...)

因此sql語句可寫成'insert into author_message(name,id,following...) values('%s','%s','%s'...)'%(item['name'],item['id'],item['following']...),但這樣寫是不安全的,若是這些字符中有sql的邊界字符,例如 ,,**等,會自動轉義爲sql禁止的符號,可能會形成安全問題。

If those strings have out-of-bounds characters like ’ or \ in, you’ve got an SQL injection leading to possible security compromise.

因此推薦使用self.cursor.execute(sql, (item['name'], item['id'], item['following']...)
成果如圖
這裏寫圖片描述

以上代碼均可做爲模板,稍做修改就可替換使用。

ok,差很少就這樣吧,看之後想到了什麼,在更新一下。

相關文章
相關標籤/搜索