本文介绍如何使用scrapy爬取博客园的文章。

具体过程:

  • 创建一个Scrapy项目
  • 定义提取的Item
  • 编写爬取网站的 spider 并提取 Item
  • 编写 Item Pipeline 来存储提取到的Item(即数据)

创建项目

在开始爬取之前,您必须创建一个新的Scrapy项目。 进入您打算存储代码的目录中,运行下列命令:

1
scrapy startproject tutorial

该命令将会创建包含下列内容的 tutorial 目录:

1
2
3
4
5
6
7
8
9
10
tutorial/
scrapy.cfg
tutorial/
__init__.py
items.py
pipelines.py
settings.py
spiders/
__init__.py
...

这些文件分别是:

  • scrapy.cfg: 项目的配置文件
  • tutorial/: 该项目的python模块。之后您将在此加入代码。
  • tutorial/items.py: 项目中的item文件.
  • tutorial/pipelines.py: 项目中的pipelines文件.
  • tutorial/settings.py: 项目的设置文件.
  • tutorial/spiders/: 放置spider代码的目录.

定义Item

Item 是保存爬取到的数据的容器;其使用方法和python字典类似, 并且提供了额外保护机制来避免拼写错误导致的未定义字段错误。

类似在ORM中做的一样,您可以通过创建一个 scrapy.Item 类, 并且定义类型为 scrapy.Field 的类属性来定义一个Item。 (如果不了解ORM, 不用担心,您会发现这个步骤非常简单)

首先根据需要从dmoz.org获取到的数据对item进行建模。 我们需要从dmoz中获取名字,url,以及网站的描述。 对此,在item中定义相应的字段。编辑 tutorial 目录中的 items.py 文件:

1
2
3
4
5
6
7
8
9
10
import scrapy

class TutorialItem(scrapy.Item):
title=scrapy.Field()
publishDate=scrapy.Field()
url=scrapy.Field()
content=scrapy.Field()
keywords=scrapy.Field()
scrapyDate=scrapy.Field()
pass

编写第一个爬虫(Spider)

Spider是用户编写用于从单个网站(或者一些网站)爬取数据的类。

其包含了一个用于下载的初始URL,如何跟进网页中的链接以及如何分析页面中的内容, 提取生成 item 的方法。

为了创建一个Spider,您必须继承 scrapy.Spider 类, 且定义以下三个属性:

  • name: 用于区别Spider。 该名字必须是唯一的,您不可以为不同的Spider设定相同的名字。
  • start_urls: 包含了Spider在启动时进行爬取的url列表。 因此,第一个被获取到的页面将是其中之一。 后续的URL则从初始的URL获取到的数据中提取。
  • parse() 是spider的一个方法。 被调用时,每个初始URL完成下载后生成的 Response 对象将会作为唯一的参数传递给该函数。 该方法负责解析返回的数据(response data),提取数据(生成item)以及生成需要进一步处理的URL的 Request 对象。

以下为我们的第一个Spider代码,保存在 tutorial/spiders 目录下的__init__.py 文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# -*- coding: utf-8 -*-
import scrapy

from tutorial.items import TutorialItem
import time
from scrapy.spiders import CrawlSpider
import re
import requests
from lxml import etree

import sys
reload(sys)
sys.setdefaultencoding('utf-8')

class spider(CrawlSpider):
name = "cnblogsSpider" # 设置爬虫名称

allowed_domains = ["cnblogs.com"] # 设置允许的域名
# start_urls = [
# "http://www.cnblogs.com/lidabo/p/7746915.html", # 设置开始爬取页面
# ]

start_urls = [
"http://www.cnblogs.com/AllBloggers.aspx", # 设置开始爬取页面
]

def parse(self, response):
sel=response.selector
userUrls=sel.xpath('//td/a[1]/@href').extract()
for userUrl in userUrls:
yield scrapy.Request(userUrl, callback=self.parse_cagetory)
print '1'

def parse_cagetory(self,response):
sel=response.selector
scr = sel.xpath('//script/text()').extract()[0]
pat = "currentBlogApp =.+'"
st = re.findall(pat, scr)[0]
currentBlogApp = st.replace("currentBlogApp = '", "").replace("'", "")

url='http://www.cnblogs.com/'+currentBlogApp+'/mvc/blog/sidecolumn.aspx?blogApp='+currentBlogApp
r = requests.get(url)
sel = etree.HTML(r.content)
categoryUrls=sel.xpath('//div[@id="sidebar_categories"]//a/@href') #no extract()
print '-------'
print len(categoryUrls)
for categoryUrl in categoryUrls:
print 'categoryUrl'+categoryUrl
yield scrapy.Request(categoryUrl,callback=self.parse_itemList)

def parse_itemList(self,response):
sel=response.selector
print '++++++'
postTitleUrls=sel.xpath('//div[@class="entrylistPosttitle"]/a/@href').extract()
for titleUrl in postTitleUrls:
print 'titleUrl'+titleUrl
yield scrapy.Request(titleUrl,callback=self.parse_item)

def parse_item(self, response):
sel = response.selector
item = TutorialItem()
print '??????????'
scr = sel.xpath('//script/text()').extract()[0]
pat = "currentBlogApp =.+'"
st = re.findall(pat, scr)[0]
currentBlogApp = st.replace("currentBlogApp = '", "").replace("'", "")

scr = sel.xpath('//script/text()').extract()[1]
pat = 'cb_blogId=.+,cb_entryId'
st = re.findall(pat, scr)[0]
cb_blogId = st.replace('cb_blogId=', '').replace(',cb_entryId', '')

scr = sel.xpath('//script/text()').extract()[1]
pat = 'cb_entryId=.+,cb_blogApp'
st = re.findall(pat, scr)[0]
cb_entryId = st.replace('cb_entryId=', '').replace(',cb_blogApp', '')

requestDataDict={}
pat = 'cb_blogId.+,cb_entryCreatedDate'
js = sel.xpath('//div[@id="topics"]/script/text()').extract_first()
strs=re.findall(pat, js)[0]
tmpList=strs.split(',')[0:4]
for s in tmpList:
emeList=s.split('=')
requestDataDict[emeList[0]]=emeList[1]

title=sel.xpath('//a[@id="cb_post_title_url"]/text()').extract_first()

# item["title"] = (title is not None and [title.decode("utf-8")] or [""])[0]
item["title"] = title
publishDate = sel.xpath('//span[@id="post-date"]/text()').extract_first()
item["publishDate"] = (publishDate is not None and [publishDate.encode("utf-8")] or [""])[0]

url=response.url
item["url"]=url


plist=sel.xpath('//div[@id="cnblogs_post_body"]//text()').extract()
content=''
for s in plist:
p = (s is not None and [s.decode("utf-8")] or [""])[0]
content=content+' '+p
item["content"]=content

r = requests.get(
'http://www.cnblogs.com/mvc/blog/CategoriesTags.aspx?blogApp='+currentBlogApp+'&blogId='+cb_blogId+'&postId='+cb_entryId)
s = r.json().get('Categories')
pat = '>.*?</a>'
lis = re.findall(pat, s)
keywords = lis[0].replace('>', '').replace('</a', '')
for i in xrange(len(lis)-1):
tmpword=lis[i+1].replace('>', '').replace('</a', '')
keywords=keywords+' '+tmpword
item["keywords"]=keywords

scrapyDate=time.strftime('%Y-%m-%d %H:%M',time.localtime(time.time()))
item["scrapyDate"]=(scrapyDate is not None and [scrapyDate.encode("utf-8")] or [""])[0]


yield item
# self.file.close()

爬取

进入项目的根目录,在命令行/终端下(假设你用的是pycharm或者mac)执行下列命令启动spider:

1
scrapy crawl cnblogsSpider    #cnblogsSpider是爬虫的名字

保存数据

在pipelines.py文件中编写以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import json

class TutorialPipeline(object):

def __init__(self):
self.file = open("item.json", "w+")

def process_item(self, item, spider):
record = json.dumps(dict(item), ensure_ascii=False)+"\n" #此处如果有中文的话,要加上ensure_ascii=False参数,否则可能出现乱码
self.file.write(record)
return item

def open_spider(self, spider):
pass

def close_spider(self, spider):
self.file.close()

参考