安裝 Scrapy 框架
pip3 install Scrapy
Scrapy架構圖(綠線是數(shù)據(jù)流向):
Scrapy Engine(引擎): 負責Spider辣垒、ItemPipeline、Downloader印蔬、Scheduler中間的通訊,信號脱衙、數(shù)據(jù)傳遞等侥猬。
Scheduler(調度器): 它負責接受引擎發(fā)送過來的Request請求多律,并按照一定的方式進行整理排列蔑赘,入隊苛预,當引擎需要時书幕,交還給引擎攀例。
Downloader(下載器):負責下載Scrapy Engine(引擎)發(fā)送的所有Requests請求劳较,并將其獲取到的Responses交還給Scrapy Engine(引擎)谱净,由引擎交給Spider來處理榨惰,
Spider(爬蟲):它負責處理所有Responses,從中分析提取數(shù)據(jù),獲取Item字段需要的數(shù)據(jù)垢油,并將需要跟進的URL提交給引擎盆驹,再次進入Scheduler(調度器),
Item Pipeline(管道):它負責處理Spider中獲取到的Item滩愁,并進行進行后期處理(詳細分析躯喇、過濾、存儲等)的地方.
Downloader Middlewares(下載中間件):你可以當作是一個可以自定義擴展下載功能的組件硝枉。
Spider Middlewares(Spider中間件):你可以理解為是一個可以自定擴展和操作引擎和Spider中間通信的功能組件(比如進入Spider的Responses;和從Spider出去的Requests)
新建項目(scrapy startproject)
在開始爬取之前廉丽,必須創(chuàng)建一個新的Scrapy項目。進入自定義的項目目錄中妻味,運行下列命令:
scrapy startproject myspider
新建爬蟲文件
** scrapy genspider jobbole jobbole.com**
關于yeild函數(shù)
參考資料說明:https://blog.csdn.net/u013205877/article/details/70332612 https://www.ibm.com/developerworks/cn/opensource/os-cn-python-yield/
簡單地講正压,yield 的作用就是把一個函數(shù)變成一個 generator(生成器),帶有 yield 的函數(shù)不再是一個普通函數(shù)责球,Python 解釋器會將其視為一個 generator蔑匣,帶有yeild的函數(shù)遇到y(tǒng)eild的時候就返回一個迭代值,下次迭代時棕诵, 代碼從 yield 的下一條語句繼續(xù)執(zhí)行裁良,而函數(shù)的本地變量看起來和上次中斷執(zhí)行前是完全一樣的,于是函數(shù)繼續(xù)執(zhí)行校套, 直到再次遇到 yield价脾。
通俗的講就是:在一個函數(shù)中,程序執(zhí)行到y(tǒng)ield語句的時候笛匙,程序暫停侨把,返回yield后面表達式的值,在下一次調用的時候妹孙,從yield語句暫停的地方繼續(xù)執(zhí)行秋柄,如此循環(huán),直到函數(shù)執(zhí)行完蠢正。
settings.py文件設置參考
- 爬蟲的文件路徑
SPIDER_MODULES = ['ziruproject.spiders']
NEWSPIDER_MODULE = 'ziruproject.spiders' - 用戶代理骇笔,一般設置這個參數(shù)用來偽裝瀏覽器請求
USER_AGENT = '' - 是否遵守ROBOT協(xié)議,為False時嚣崭,表示不遵守笨触, 為True時表示遵守(默認為True)
ROBOTSTXT_OBEY = True
Scrapy downloader(下載器) 處理的最大的并發(fā)請求數(shù)量。 默認: 16
CONCURRENT_REQUESTS - 下載延遲的秒數(shù)雹舀,用來限制訪問的頻率
默認為:0
DOWNLOAD_DELAY
scrapy案例
以'http://chinaz.com/'為例
(下載項目圖片以及實現(xiàn)爬蟲數(shù)據(jù)持久化保存)
chinaz.py
# -*- coding: utf-8 -*-
import scrapy
from chinaz.items import ChinazprojectItem, ChinazprojectWebInfoItem
class ChinazSpider(scrapy.Spider):
#爬蟲名稱
name = 'china'
#設置允許爬取的域
allowed_domains = ['chinaz.com']
#設置起始urls
start_urls = ['http://top.chinaz.com/hangyemap.html']
# 可以根據(jù)不同的爬蟲文件做自定義的參數(shù)設置芦劣,會覆蓋settings.py中的相關設置
custom_settings = {
'USER_AGENT' : 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'
}
def parse(self, response):
"""
在parse回調方法中
step1:提取目標數(shù)據(jù)
step2:獲取新的url
:param response: 請求的響應結果
:return:
"""
print(response.status)
# response.xpath():使用xpath語法,得到的是SelectorList對象
# response.css():使用css選擇器,得到的是SelectorList對象
# extract(): 將selector 序列化為unicode字符串
# step1: 提取目標數(shù)據(jù)
# 獲取分類列表
tags = response.xpath('//div[@class="Taright"]/a')
# tags = response.css('.Taright a')
for tag in tags:
#實例化一個item,用來存儲數(shù)據(jù)
tag_item = ChinazprojectItem()
#獲取網(wǎng)站分類的名稱
# tagName = tag.xpath('./text()')[0].extract()
tagName = tag.xpath('./text()').extract_first('')
tag_item['tagName'] = tagName
# 使用css取值(文本)
# tagName = tag.css('::text').extract_first('')
#獲取網(wǎng)站分了的首頁url地址
# first_url = tag.xpath('./@href')[0].extract()
first_url = tag.xpath('./@href').extract_first('')
tag_item['firsturl'] = first_url
#css語法取值(屬性)
# first_url = tag.css('::attr(href)').extract_first('')
# print(tag_item)
#將獲取到的數(shù)據(jù)交給管道處理
yield tag_item
# http://top.chinaz.com/hangye/index_yule_yinyue.html
'''
url : 設置需要發(fā)起請求的url地址
callback=None, : 設置請求成功后的回調方法
method='GET', : 請求方式,默認為get請求
headers=None, : 設置請求頭说榆,字典類型
cookies=None, : 設置cookie信息虚吟,模擬登陸用戶寸认,字典類型
meta=None, : 傳參,字典類型
encoding='utf-8', : 設置編碼
dont_filter=False, : 是否要去重串慰,默認False表示去重
errback=None, : 設置請求失敗后的回調
'''
yield scrapy.Request(first_url,callback=self.parse_tags_page)
def parse_tags_page(self,response):
'''
解析分類分頁的網(wǎng)站信息
:param response : 響應結果
:return:
'''
print('分頁請求', response.status, response.url)
# 列表
webInfos = response.xpath('//ul[@class="listCentent"]/li')
for webinfo in webInfos:
web = ChinazprojectWebInfoItem()
# 封面圖片
web['coverImage'] = webinfo.xpath('//div[@class="leftImg"]/a/img/@src').extract_first('')
# web['coverImages'] = ['']
# 標題
web['title'] = webinfo.xpath('//div[@class="CentTxt"]/h3/a/text()').extract_first('')
# 域名
web['domenis'] = webinfo.xpath('//div[@class="CentTxt"]/h3/span/text()').extract_first('')
# 周排名
web['weekRank'] = webinfo.xpath('//div[@class="CentTxt"]/div[@class="RtCPart clearfix"]/p[0]//a/text()').extract_first('')
# 反鏈數(shù)
web['ulink'] = webinfo.xpath('//div[@class="CentTxt"]/div[@class="RtCPart clearfix"]//p[3]//a/text()').extract_first('')
# 網(wǎng)站簡介
web['info'] = webinfo.xpath('//div[@class="CentTxt"]/p/text()').extract_first('')
# 得分
web['score'] = webinfo.xpath('//div[@class="RtCRateCent"]/span/text()').extract_first('')
# 排名
web['rank'] = webinfo.xpath('//div[@class="RtCRateCent"]/strong/text()').extract_first('')
# print(web)
yield web
#發(fā)起其他頁面請求
next_urls = response.xpath('//div[@class="ListPageErap"]/a/@href').extract()[1:]
for next_url in next_urls:
# 使用urljoin方法將不完整的url拼接完整
next_url = response.urljoin(next_url)
yield scrapy.Request(next_url,callback = self.parse_tags_page)
items.py
# -*- coding: utf-8 -*-
# Define here the models for your scraped items
# See documentation in:
# https://doc.scrapy.org/en/latest/topics/items.html
import scrapy
class ChinazprojectItem(scrapy.Item):
'''
存儲網(wǎng)頁分類信息
'''
# 分類名稱
tagName = scrapy.Field()
# 分類首頁url地址
firsturl = scrapy.Field()
def get_insert_spl_data(self, dataDict):
'''
step1 : 創(chuàng)建SQL語句
step2 : 返回要存儲的數(shù)據(jù)
:param dataDict:
:return:
'''
# 往數(shù)據(jù)庫寫
sql = """
INSERT INTO tags(%s)
VALUES (%s)
""" % (
','.join(dataDict.keys()),
','.join(['%s'] * len(dataDict))
)
# 需要往數(shù)據(jù)庫中存儲的數(shù)據(jù)
data = list(dataDict.values())
return sql, data
class ChinazprojectWebInfoItem(scrapy.Item):
# 封面圖片
coverImage = scrapy.Field()
# 標題
title = scrapy.Field()
# 域名
domenis = scrapy.Field()
# 周排名
weekRank = scrapy.Field()
# 反鏈接數(shù)
ulink = scrapy.Field()
# 網(wǎng)站簡介
info = scrapy.Field()
# 得分
score = scrapy.Field()
# 排名
rank = scrapy.Field()
# 圖片本地存儲路徑
locakImagePath = scrapy.Field()
pipelines.py
# -*- coding: utf-8 -*-
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html
import mysql.connector as c
import pymongo
from scrapy.contrib.pipeline.images import ImagesPipeline
import scrapy
from chinaz.items import ChinazprojectWebInfoItem, ChinazprojectItem
from scrapy.utils.project import get_project_settings
import os
images_store = get_project_settings().get('IMAGES_STORE')
class ChinazProjectImagePipeline(ImagesPipeline):
# 實現(xiàn)2個方法
def get_media_requests(self, item, info):
'''
根據(jù)圖片的url地址構造request請求
:param item:
:param info:
:return:
'''
if isinstance(item,ChinazprojectWebInfoItem):
# 獲取圖片地址
image_url = 'http:' + item['coverImage']
print('獲取到圖片地址', image_url)
yield scrapy.Request(image_url)
# 如果有多個圖片地址item['coverImage']對應一個列表
# image_urls = 'http:' + item['coverImage']
# return [scrapy.Request(x) for x in image_urls]
def item_completed(self, results, item, info):
'''
圖片下載之后的回調方法
:param results:[(True(表示圖片是否下載成功),{'path':'圖片下載之后的存儲路徑','url'.'圖片url地址','checksum':'經(jīng)過hash加密的一個字符串'})]
:param item:
:param info:
:return:
'''
if isinstance(item,ChinazprojectWebInfoItem):
paths = [result['path'] for status,result in results if status]
print('圖片下載成功', paths)
if len(paths) > 0:
print('圖片獲取成功')
# 使用rname方法修改圖片名稱
os.rename(images_store + '/' + paths[0],images_store + '/' + item['title'] + '.jpg')
image_path = images_store + '/' + item['title'] + '.jpg'
print('修改后的圖片路徑', image_path)
item['localImagepath'] = image_path
else:
# 如果沒有獲取到圖片吧這個item丟棄
from scrapy.exceptions import DropItem
raise DropItem('沒有獲取到圖片,丟棄item')
return item
# class ChinazprojectPipeline(object):
#
# def __init__(self):
# """
# 初始化方法
# """
# # self.file = open('chainz.json','a')
# # 創(chuàng)建數(shù)據(jù)庫連接
# self.client = c.Connect(
# host = '127.0.0.1', user = 'root', password = '123456',
# database = 'chinaz', port = 3306, charset='utf8'
# )
# #創(chuàng)建游標
# self.cursor = self.client.cursor()
#
# def open_spider(self,spider):
# """
# 爬蟲啟動的時候會調用一次
# :param spider:
# :return:
# """
# print('爬蟲開啟')
#
# def process_item(self, item, spider):
# """
# 這個方法是必須實現(xiàn)的,爬蟲文件中所有的item
# 都會經(jīng)過這個方法
# :param item: 爬蟲文件傳遞過來的item對象
# :param spider: 爬蟲文件實例化的對象
# :return:
# """
# #存儲到本地json文件
# data_dict = dict(item)
#
# # import json
# # json_data = json.dumps(data_dict,ensure_ascii=False)
# # self.file.write(json_data+'\n')
# # 使用isinstance判斷item要存儲的表
# # if isinstance(item, ChinazprojectWebInfoItem):
# # print('網(wǎng)站信息')
# # tablename = 'webinfo'
# # elif isinstance(item, ChinazprojectItem):
# # print('網(wǎng)站分類信息')
# # tablename = 'tags'
# #往數(shù)據(jù)庫寫
# # sql = """
# # INSERT INTO tags(%s)
# # VALUES (%s)
# # """ %(
# # ','.join(data_dict.keys()),
# # ','.join(['%s']*len(data_dict))
# # )
# if data_dict:
#
# sql, data = item.get_insert_spl_data(data_dict)
# try:
# # self.cursor.execute(sql,list(data_dict.values()))
# self.cursor.execute(sql, data)
# self.client.commit()
# except Exception as err:
# self.client.rollback()
# print(err)
#
# #如果有多個管道文件,一定要注意return item,
# #否則下一個管道無法接收到item
# print('經(jīng)過了管道文件')
# return item
#
# def close_spider(self,spider):
# """
# 爬蟲結束的時候會調用一次
# :param spider:
# :return:
# """
# # self.file.close()
# self.cursor.close()
# self.client.close()
# print('爬蟲結束')
# 往MongoDB中插入數(shù)據(jù)
# class ChinazprojectPipeline(object):
#
# def __init__(self,host,port,db):
# #創(chuàng)建MongoDB的數(shù)據(jù)庫鏈接
# self.mongo_client = pymongo.MongoClient(
# host='127.0.0.1',port=27017
# )
# # 獲取要操作的數(shù)據(jù)庫
# self.db = self.mongo_client['db']
#
# @classmethod
# def from_crawler(cls,crawler):
# '''
# MONGODB_HOST = '127.0.0.1'
# MONGODB_PORT= 27017
# MONGODB_DB = "chinaz"
# :param crawler:
# :return:
# '''
# host = crawler.settings['MONGODB_HOST']
# port = crawler.settings['MONGODB_PORT']
# db = crawler.settings['MONGODB_DB']
# return cls(host,port,db)
#
# def process_item(self, item, spider):
# '''
# 這個方法是必須實現(xiàn)的,爬蟲文件中所有的item
# 都會經(jīng)過這個方法
# :param item: 爬蟲文件傳遞過來的item對象
# :param spider: 爬蟲文件實例化的對象
# :return:
# '''
# # 往哪個集合下插入數(shù)據(jù)
# # 往集合下插入什么數(shù)據(jù)
# col_name = item.get_mongodb_collectionName()
# col = self.db[col_name]
# dict_data = dict(item)
# try:
# col.insert(dict_data)
# print('數(shù)據(jù)插入成功')
# except Exception as err:
# print('數(shù)據(jù)插入失敗',err)
# return item
#
# def open_spider(self,spider):
# print(spider.name,'爬蟲開始運行')
#
# def close_spider(self, spider):
# # self.file.close()
# self.cursor.close()
# self.client.close()
# print('爬蟲結束')
#實現(xiàn)mysql數(shù)據(jù)庫的異步插入(要插入的數(shù)據(jù)量非常大的情況下)
from twisted.enterprise import adbapi
class ChinazprojectPipeline(object):
def __init__(self,dbpool):
self.dbpool = dbpool
@classmethod
def from_crawler(cls,cralwer):
"""
MYSQL_HOST = '127.0.0.1'
MYSQL_USER = 'root'
MYSQL_PWD = 'ljh1314'
MYSQL_DB = 'chainz'
MYSQL_PORT = 3306
MYSQL_CHARSET = 'utf8'
:param cralwer:
:return:
"""
db_parmars = {
'host':cralwer.settings['MYSQL_HOST'],
'user':cralwer.settings['MYSQL_USER'],
'passwd':cralwer.settings['MYSQL_PWD'],
'db':cralwer.settings['MYSQL_DB'],
'port':cralwer.settings['MYSQL_PORT'],
'charset':cralwer.settings['MYSQL_CHARSET'],
}
dbpool = adbapi.ConnectionPool('pymysql',**db_parmars)
return cls(dbpool)
def process_item(self,item,spider):
query = self.dbpool.runInteraction(
self.insert_data_to_mysql,
item
)
query.addErrback(
self.insert_err,
item
)
return item
def insert_data_to_mysql(self,cursor,item):
data_dict = dict(item)
sql,data = item.get_insert_sql_data(data_dict)
cursor.execute(sql,data)
def insert_err(self,failure,item):
print(failure,'插入失敗',item)
scrapy shell
Scrapy終端是一個交互終端偏塞,我們可以在未啟動spider的情況下嘗試及調試代碼,也可以用來測試XPath或CSS表達式模庐,查看他們的工作方式烛愧,方便我們爬取的網(wǎng)頁中提取的數(shù)據(jù)。
啟動Scrapy Shell
scrapy shell "http://www.baidu.com/"
scrapy shell -s USER_AGENT=' '
Scrapy Shell根據(jù)下載的頁面會自動創(chuàng)建一些方便使用的對象掂碱,例如 Response 對象怜姿,以及 Selector 對象 (對HTML及XML內容)。
當shell載入后疼燥,將得到一個包含response數(shù)據(jù)的本地 response 變量沧卢,輸入 response.body將輸出response的包體,輸出 response.headers 可以看到response的包頭醉者。
輸入 response.selector 時但狭, 將獲取到一個response 初始化的類 Selector 的對象,此時可以通過使用 response.selector.xpath()或response.selector.css() 來對 response 進行查詢撬即。
Scrapy也提供了一些快捷方式, 例如 response.xpath()或response.css()同樣可以生效(如之前的案例)立磁。
Selectors選擇器 Scrapy Selectors 內置 XPath 和 CSS Selector 表達式機制 Selector有四個基本的方法,最常用的還是xpath:
xpath(): 傳入xpath表達式剥槐,返回該表達式所對應的所有節(jié)點的selector list列表
extract(): 序列化該節(jié)點為字符串并返回list
css(): 傳入CSS表達式唱歧,返回該表達式所對應的所有節(jié)點的selector list列表,語法同 BeautifulSoup4
re(): 根據(jù)傳入的正則表達式對數(shù)據(jù)進行提取粒竖,返回字符串list列表
僅為個人學習小結颅崩,若有錯處,歡迎指正~