在上一篇文章中,談及瞭如何使用scrapy取獲取網頁數據,可參考Scrapy入門學習之初步探索。在此繼續談一下數據保存和一些settings.py的配置。html
文中會繼續上文的豆瓣讀書小項目,並新增一個爬取簡書全部用戶信息的爬蟲小項目,文章力求可以講清學習時所遇到的坑,因此略顯冗長。如若您沒有耐心,就別往下翻了O(∩_∩)O~python
環境申明:
Python3+Pycharm2017
所需第三方包:
scrapy,pymysql
數據庫:
MySQL5.7mysql
學習爬蟲主要是爲了收集一些數據用於分析,而用的多的數據格式無非就是json,csv或是經過數據庫MySQL,mongodb等。ajax
在此,系統的記錄相應的數據格式處理方式。sql
OK,這裏你須要作的有:mongodb
首先scrapy自帶了相應模塊用於處理數據,在item export中數據庫
- ‘CsvItemExporter’,
- ‘JsonItemExporter’
scrapy文檔使用 Item Exporter
建議直接閱讀源碼django
在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
注意事項:
encoding=’utf-8’,碰到了excel中打開csv中文沒法正常顯示的狀況,如圖。目前解決方法有:
用記事本、sublime或pycharm打開,能夠正常顯示。再以ANSI或utf8-bom編碼保存,再次用excel打開正常顯示
同樣要在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。
更多內容能夠閱讀
【2】Python Scrapy: How to get CSVItemExporter to write columns in a specific order
這裏咱們再也不爬取豆瓣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條數據
直接上代碼吧,
# -*- 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)
如同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()
能夠直接套用上面的模板,稍做修改便可
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
重寫下載中間件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,
}
一樣是用上文的簡書做者信息爲列,
在查了一些資料後,發現了一些可能會碰到的問題。如寫入文件編碼的問題等。
這裏導入模塊
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()
注意事項:
ensure_ascii=False
,這裏不寫成False,寫入文件時會把unicode編碼格式寫入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有關)
我也是初步學習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,差很少就這樣吧,看之後想到了什麼,在更新一下。