- Scrapy是用純python實(shí)現(xiàn)一個(gè)為了爬取網(wǎng)站數(shù)據(jù)焕数、提取結(jié)構(gòu)性數(shù)據(jù)而編寫的應(yīng)用框架,用途非常廣泛
-
Scrapy架構(gòu)圖(綠線是數(shù)據(jù)流向):
- Scrapy Engine(引擎):負(fù)責(zé)Spider、ltemPipeline茵烈、Downloader百匆、Scheduler中間的通訊,信號(hào)、數(shù)據(jù)傳遞等
- Scheduler(調(diào)度器):它負(fù)責(zé)接受引擎發(fā)送過(guò)來(lái)的Request請(qǐng)求,并按照一定的方式進(jìn)行整理排列,
入隊(duì),當(dāng)引擎需要時(shí),交還給引擎. - Downloader(下載器):負(fù)責(zé)下載Scrapy Engine(引擎),由引擎交給Spider來(lái)處理
- Spider(爬蟲):它負(fù)責(zé)處理所有Responses,從中分析提取數(shù)據(jù),獲取litem字段需要的數(shù)據(jù),并將需要跟進(jìn)的URL提交給引擎,再次進(jìn)入Scheduler(調(diào)度器)
- ltem Pipeline(管道):它負(fù)責(zé)處理Spider中獲取到的ltem,并進(jìn)行進(jìn)行后期處理(詳細(xì)分呜投、過(guò)濾加匈、存儲(chǔ)等)的地方
- Downloader Middlewares(下載中間件):你可以當(dāng)作是一個(gè)可以自定義擴(kuò)展下載功能的組件。
- Spider Middlewares(Spider中間件):你可以理解為是一個(gè)可以自定擴(kuò)展和操作引擎和Spider中間通信的功能組件(比如進(jìn)入Spider的Responses;和從Spider出去的Requests)
一仑荐、新建項(xiàng)目(scrapy starproject)
- 創(chuàng)建爬蟲項(xiàng)目
scrapy startproject項(xiàng)目名稱(有些用戶需要前面加上python -m)
- 新建爬蟲文件
scrapy genspider爬蟲文件 域名
- item定義結(jié)構(gòu)化數(shù)據(jù)字段,用來(lái)保存爬取到的數(shù)據(jù),有點(diǎn)像Python中的dict,但是提供了一些額外的保護(hù)減少錯(cuò)誤
item文件
class JobboleItem(scrapy.Item):
# define the fields for your item here like:
#標(biāo)題
title = scrapy.Field()
#創(chuàng)建時(shí)間
create_date = scrapy.Field()
#文章地址
url = scrapy.Field()
#id
url_object_id = scrapy.Field()
#文章圖片
front_image_url = scrapy.Field()
#文章圖片地址
front_image_path = scrapy.Field()
# 點(diǎn)贊數(shù)
praise_nums = scrapy.Field()
#收藏?cái)?shù)
bookmark_nums = scrapy.Field()
二雕拼、制作爬蟲
# -*- coding: utf-8 -*-
import scrapy
class JobboleSpider(scrapy.Spider):
name = '爬蟲文件'
allowed_domains = ['網(wǎng)站域名']
start_urls = ['https://xxx.xxxx.com/xxxx-x/']
def parse(self, response):
pass
- name = "":這個(gè)爬蟲的識(shí)別名稱,必須是唯一的,在不同的爬蟲必須定義不同的名字.
- allow_domains=[]是搜索的域名范圍,也就是爬蟲的約束區(qū)域,規(guī)定爬蟲只爬取這個(gè)域名下的網(wǎng)頁(yè),不存在的URL會(huì)被忽略
- staart_urls=():爬取的URL元組/列表,爬取從這里開始抓取數(shù)據(jù),所以,第一次下載的數(shù)據(jù)將會(huì)從這些urls開始。其他子URL將會(huì)從這些起始URL中繼承性生成粘招。
- oarse(self,response):解析的方法,每個(gè)初始URL完成下載后將被調(diào)用,調(diào)用的時(shí)候傳入從每個(gè)URL傳回的Response對(duì)象來(lái)作為唯一參數(shù)
三啥寇、在parse方法中做數(shù)據(jù)的提取
from jobboleproject.items import JobboleprojectItem
1.獲取圖片和文章詳情的鏈接
def parse(self, response):
# css選擇器獲取當(dāng)前列表頁(yè)面的所有的節(jié)點(diǎn)
post_nodes = response.css("#archive .floated-thumb .post-thumb a")
# 如果不是完整的域名 需要拼接完整 response.url + post_url
# 獲取到的URL可能不是一個(gè)域名,也可能是具體的文章需要使用parse函數(shù)from urllib import parse
for post_node in post_nodes:
image_url = post_node.css("img::attr(src)").extract_first("")
post_url = post_node.css("::attr(href)").extract_first("")
full_url = response.urljoin(post_url)
#meta參數(shù)對(duì)應(yīng)的是一個(gè)字典,用來(lái)傳遞數(shù)據(jù)
yield scrapy.Request(url=full_url, meta={"front_image_url": image_url},
callback=self.parse_detail)
2.然后將我們得到的數(shù)據(jù)封裝到一個(gè) JobboleItem 對(duì)象中辑甜,可以保存每個(gè)文章的屬性:
def parse_detail(self,response):
# print(response)
# 使用xpath語(yǔ)法或者css語(yǔ)法提取網(wǎng)頁(yè)的相關(guān)信息
# extract() 串行化并將匹配到的節(jié)點(diǎn)返回一個(gè)unicode字符串列表
item = JobboleprojectItem()
#標(biāo)題
item['title'] = response.xpath('//div[@class="entry-header"]/h1/text()').extract_frist("")
#發(fā)布日期
item['create_date'] = response.xpath("http://p[@class='entry-meta-hide-on-mobile']/text()").extract_first("").strip().replace("·","").strip()
#文章地址詳情地址
item['url'] = response.url
# http://blog.jobbole.com/113949/
#文章的id
item['url_object_id'] = re.match(".*?(\d+).*", url).group(1)
# 文章圖片
item['front_image_url'] = response.meta.get("front_image_url","")
# 點(diǎn)贊數(shù)
item['praise_nums'] = response.xpath("http://span[contains(@class,'vote-post-up')]/h10/text()").extract_first("")
# 收藏?cái)?shù)
bookmark_nums = response.xpath("http://span[contains(@class,'bookmark-btn')]/text()").extract_first("")
match_bookmark_nums = re.match(".*?(\d+).*",bookmark_nums)
if match_bookmark_nums:
item['bookmark_nums'] = int(match_bookmark_nums.group(1))
else:
item['bookmark_nums'] = 0
# 評(píng)論數(shù)
comment_nums = response.xpath("http://a[@href='#article-comment']/span/text()").extract_first("")
match_comment_nums = re.match(".*?(\d+).*",comment_nums)
if match_comment_nums:
item['comment_nums'] = int(match_comment_nums.group(1))
else:
item['comment_nums'] = 0
# 文章內(nèi)容
item['content'] = response.xpath("http://div[@class='entry']").extract_first("")
# 過(guò)濾評(píng)論標(biāo)簽
tag_list = response.xpath("http://p[@class='entry-meta-hide-on-mobile']//a/text()").extract()
tag_list = [element for element in tag_list if not element.strip().endswith("評(píng)論")]
# 標(biāo)簽
item['tags'] = ",".join(tag_list)
print(item)
# return返回?cái)?shù)據(jù)衰絮,不經(jīng)過(guò)pipelines
# return item
# yield將獲取的數(shù)據(jù)交給pipelines
# yield item
yidld的作用就是把一個(gè)函數(shù)變成一個(gè)generator(生成器),帶有yield的函數(shù)不再是一個(gè)普通函數(shù),Python解釋器會(huì)將其視為一個(gè)generator
四厨钻、編寫ltemPipeline
import something
class SomethingPipeline(object):
def __init__(self):
# 可選實(shí)現(xiàn)炼幔,做參數(shù)初始化等
# doing something
def process_item(self, item, spider):
# item (Item 對(duì)象) – 被爬取的item
# spider (Spider 對(duì)象) – 爬取該item的spider
# 這個(gè)方法必須實(shí)現(xiàn),每個(gè)item pipeline組件都需要調(diào)用該方法捆昏,
# 這個(gè)方法必須返回一個(gè) Item 對(duì)象邓线,被丟棄的item將不會(huì)被之后的pipeline組件所處理淌友。
return item
def open_spider(self, spider):
# spider (Spider 對(duì)象) – 被開啟的spider
# 可選實(shí)現(xiàn),當(dāng)spider被開啟時(shí)骇陈,這個(gè)方法被調(diào)用震庭。
def close_spider(self, spider):
# spider (Spider 對(duì)象) – 被關(guān)閉的spider
# 可選實(shí)現(xiàn),當(dāng)spider被關(guān)閉時(shí)你雌,這個(gè)方法被調(diào)用
管道作用:
- 驗(yàn)證爬取的數(shù)據(jù)(檢查item包含某些字段,比如說(shuō)name字段)
- 查重(并丟棄)
- 將爬取結(jié)果保存到文件或者數(shù)據(jù)庫(kù)中
五器联、重新啟動(dòng)爬蟲
scrapy crawl jobbole