Scrapy是一个优秀的Python爬虫框架,可以很方便的爬取web站点的信息供我们分析和挖掘,在这记录下最近使用的一些心得。

1.安装

通过pip或者easy_install安装:
1
sudo pip install scrapy

2.创建爬虫项目

1
scrapy startproject youProjectName

3.抓取数据

首先在items.py里定义要抓取的内容,以豆瓣美女为例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from scrapy.item import Field,Item

class DoubanmeinvItem(Item):
feedId = Field() #feedId
userId = Field() #用户id
createOn = Field() #创建时间
title = Field() #feedTitle
thumbUrl = Field() #feed缩略图url
href = Field() #feed链接
description = Field() #feed简介
pics = Field() #feed的图片列表
userInfo = Field() #用户信息 class UserItem(Item):
userId = Field() #用户id
name = Field() #用户name
avatar = Field() #用户头像
创建爬虫文件,cd到工程文件夹下后输入命令:
1
scrapy crawl XXX(爬虫名字)

另外可以在该爬虫项目的根目录创建一个main.py,然后在pycharm设置下运行路径

那么就不用每次都运行上面那行代码,直接运行main.py就能启动爬虫了

输入代码:

from scrapy import cmdline
cmdline.execute('scrapy crawl amazon_products -o items.csv -t csv'.split())
#-o 代表输出文件 -t 代表文件格式

  

接着编辑爬虫文件,实例如下:
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
# -*- coding: utf-8 -*-
import scrapy
import re
from DoubanMeinv.items import DoubanmeinvItem,UserItem
import json
import time
from datetime import datetime
from scrapy.exceptions import CloseSpider import sys
reload(sys)
sys.setdefaultencoding('utf8') class DbmeinvSpider(scrapy.Spider):
name = "dbMeinv"
allowed_domains = ["www.dbmeinv.com"]
start_urls = (
'http://www.dbmeinv.com/dbgroup/rank.htm?pager_offset=1',
)
baseUrl = 'http://www.dbmeinv.com'
close_down = False def parse(self, response):
request = scrapy.Request(response.url,callback=self.parsePageContent)
yield request #解析每一页的列表
def parsePageContent(self, response):
for sel in response.xpath('//div[@id="main"]//li[@class="span3"]'):
item = DoubanmeinvItem()
title = sel.xpath('.//div[@class="bottombar"]//a[1]/text()').extract()[0]
#用strip()方法过滤开头的\r\n\t和空格符
item['title'] = title.strip()
item['thumbUrl'] = sel.xpath('.//div[@class="img_single"]//img/@src').extract()[0]
href = sel.xpath('.//div[@class="img_single"]/a/@href').extract()[0]
item['href'] = href
#正则解析id
pattern = re.compile("dbgroup/(\d*)")
res = pattern.search(href).groups()
item['feedId'] = res[0]
#跳转到详情页面
request = scrapy.Request(href,callback=self.parseMeinvDetailInfo)
request.meta['item'] = item
yield request
#判断是否超过限制应该停止
if(self.close_down == True):
print "数据重复,close spider"
raise CloseSpider(reason = "reach max limit")
else:
#获取下一页并加载
next_link = response.xpath('//div[@class="clearfix"]//li[@class="next next_page"]/a/@href')
if(next_link):
url = next_link.extract()[0]
link = self.baseUrl + url
yield scrapy.Request(link,callback=self.parsePageContent) #解析详情页面
def parseMeinvDetailInfo(self, response):
item = response.meta['item']
description = response.xpath('//div[@class="panel-body markdown"]/p[1]/text()')
if(description):
item['description'] = description.extract()[0]
else:
item['description'] = ''
#上传时间
createOn = response.xpath('//div[@class="info"]/abbr/@title').extract()[0]
format = "%Y-%m-%d %H:%M:%S.%f"
t = datetime.strptime(createOn,format)
timestamp = int(time.mktime(t.timetuple()))
item['createOn'] = timestamp
#用户信息
user = UserItem()
avatar = response.xpath('//div[@class="user-card"]/div[@class="pic"]/img/@src').extract()[0]
name = response.xpath('//div[@class="user-card"]/div[@class="info"]//li[@class="name"]/text()').extract()[0]
home = response.xpath('//div[@class="user-card"]/div[@class="opt"]/a[@target="_users"]/@href').extract()[0]
user['avatar'] = avatar
user['name'] = name
#正则解析id
pattern = re.compile("/users/(\d*)")
res = pattern.search(home).groups()
user['userId'] = res[0]
item['userId'] = res[0]
#将item关联user
item['userInfo'] = user
#解析链接
pics = []
links = response.xpath('//div[@class="panel-body markdown"]/div[@class="topic-figure cc"]')
if(links):
for a in links:
img = a.xpath('./img/@src')
if(img):
picUrl = img.extract()[0]
pics.append(picUrl)
#转成json字符串保存
item['pics'] = json.dumps(list(pics))
yield item
需要说明的几点内容:
  • allowed_domin指定Spider在哪个网站爬取数据
  • start_urls包含了Spider在启动时进行爬取的url列表
  • parse方法继承自父类,每个初始URL完成下载后生成的Response对象将会作为唯一的参数传递给该函数。该方法负责解析返回的数据(response),提取数据(生成item)以及生成需要进一步处理的URL的Request对象
  • xpath解析数据的时候使用(也可以使用css),关于xpath和css的详细用法请自行搜索
  • xpath从某个子元素里解析数据时要使用element.xpath('./***')而不能使用element.xpath('/***'),否则是从最外层解析而不是从element下开始解析
  • web站点爬取的text经常包含了我们不想要的\r\n\t或者是空格等字符,这个时候就要使用Python的strip()方法来过滤掉这些数据
  • 抓取的web页面时间经常是2015-10-1 12:00:00格式,但是我们存储到数据库时要想转成timeStamp的格式,这里用Python的time相关类库来处理,代码见上面
  • 抓取完某个页面的时候,可能我们还需要抓取跟它相关的详情页面数据,这里用生成Scrapy.Request的方式来继续抓取,并且将当前的item存储到新的request的meta数据中以供后面的代码中读取到已抓取的item
  • 如果我们想要在某些情况下停止Spider的抓取,在这里设置一个flag位,并在适当的地方抛出一个CloseSpider的异常来停止爬虫,后面会接着提到这个技巧

4.运行爬虫

1
scrapy crawl youSpiderName

5.编写Pipeline

如果我们要将数据存储到MySQL数据库中,需要安装MySQLdb,安装过程很多坑,遇到了再Google解决吧。一切搞定之后开始编写pipelines.py和settings.py文件
首先在settings.py文件中定义好连接MySQL数据库的所需信息,如下所示:
1
2
3
4
5
6
7
8
9
10
DB_SERVER = 'MySQLdb'
DB_CONNECT = {
'host' : 'localhost',
'user' : 'root',
'passwd' : '',
'port' : 3306,
'db' :'dbMeizi',
'charset' : 'utf8',
'use_unicode' : True
}
然后编辑pipelines.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
from scrapy.conf import settings
from scrapy.exceptions import DropItem
from twisted.enterprise import adbapi
import json class DoubanmeinvPipeline(object):
#插入的sql语句
feed_key = ['feedId','userId','createOn','title','thumbUrl','href','description','pics']
user_key = ['userId','name','avatar']
insertFeed_sql = '''insert into MeiziFeed (%s) values (%s)'''
insertUser_sql = '''insert into MeiziUser (%s) values (%s)'''
feed_query_sql = "select * from MeiziFeed where feedId = %s"
user_query_sql = "select * from MeiziUser where userId = %s"
feed_seen_sql = "select feedId from MeiziFeed"
user_seen_sql = "select userId from MeiziUser"
max_dropcount = 50
current_dropcount = 0 def __init__(self):
dbargs = settings.get('DB_CONNECT')
db_server = settings.get('DB_SERVER')
dbpool = adbapi.ConnectionPool(db_server,**dbargs)
self.dbpool = dbpool
#更新看过的id列表
d = self.dbpool.runInteraction(self.update_feed_seen_ids)
d.addErrback(self._database_error)
u = self.dbpool.runInteraction(self.update_user_seen_ids)
u.addErrback(self._database_error) def __del__(self):
self.dbpool.close() #更新feed已录入的id列表
def update_feed_seen_ids(self, tx):
tx.execute(self.feed_seen_sql)
result = tx.fetchall()
if result:
#id[0]是因为result的子项是tuple类型
self.feed_ids_seen = set([int(id[0]) for id in result])
else:
#设置已查看过的id列表
self.feed_ids_seen = set() #更新user已录入的id列表
def update_user_seen_ids(self, tx):
tx.execute(self.user_seen_sql)
result = tx.fetchall()
if result:
#id[0]是因为result的子项是tuple类型
self.user_ids_seen = set([int(id[0]) for id in result])
else:
#设置已查看过的id列表
self.user_ids_seen = set() #处理每个item并返回
def process_item(self, item, spider):
query = self.dbpool.runInteraction(self._conditional_insert, item)
query.addErrback(self._database_error, item) feedId = item['feedId']
if(int(feedId) in self.feed_ids_seen):
self.current_dropcount += 1
if(self.current_dropcount >= self.max_dropcount):
spider.close_down = True
raise DropItem("重复的数据:%s" % item['feedId'])
else:
return item #插入数据
def _conditional_insert(self, tx, item):
#插入Feed
tx.execute(self.feed_query_sql, (item['feedId']))
result = tx.fetchone()
if result == None:
self.insert_data(item,self.insertFeed_sql,self.feed_key)
else:
print "该feed已存在数据库中:%s" % item['feedId']
#添加进seen列表中
feedId = item['feedId']
if int(feedId) not in self.feed_ids_seen:
self.feed_ids_seen.add(int(feedId))
#插入User
user = item['userInfo']
tx.execute(self.user_query_sql, (user['userId']))
user_result = tx.fetchone()
if user_result == None:
self.insert_data(user,self.insertUser_sql,self.user_key)
else:
print "该用户已存在数据库:%s" % user['userId']
#添加进seen列表中
userId = user['userId']
if int(userId) not in self.user_ids_seen:
self.user_ids_seen.add(int(userId)) #插入数据到数据库中
def insert_data(self, item, insert, sql_key):
fields = u','.join(sql_key)
qm = u','.join([u'%s'] * len(sql_key))
sql = insert % (fields,qm)
data = [item[k] for k in sql_key]
return self.dbpool.runOperation(sql,data) #数据库错误
def _database_error(self, e):
print "Database error: ", e
说明几点内容:
  • process_item:每个item通过pipeline组件都需要调用该方法,这个方法必须返回一个Item对象,或者抛出DropItem异常,被丢弃的item将不会被之后的pipeline组件所处理。
  • 已经抓取到的数据不应该再处理,这里创建了两个ids_seen方法来保存已抓取的id数据,如果已存在就Drop掉item
  • 如果重复抓取的数据过多时,这里设置了个上限值(50),如果超过了上限值就改变spider的关闭flag标志位,然后spider判断flag值在适当的时候抛出CloseSpider异常,关闭Spider代码见爬虫文件。这里通过设置flag标志位的方式来关闭爬虫主要是因为我测试的时候发现在pipelines中调用停止爬虫的方法都不起效果,故改成这种方式
  • 因为Scrapy是基于twisted的,所以这里用adbapi来连接并操作MySQL数据库
最后在settings.py文件中启用pipeline
1
2
3
4
ITEM_PIPELINES = {
'DoubanMeinv.pipelines.DoubanmeinvPipeline': 300,
# 'DoubanMeinv.pipelines.ImageCachePipeline': 500,
}

6.变换User-Agent,避免爬虫被ban

我们抓取的网站可能会检查User-Agent,所以为了爬虫正常运行我们需要设置请求的User-Agent。对于频繁的请求,还要对User-Agent做随机变换以防被ban,这里通过设置Downloader Middleware来修改爬虫的request和respons
在setting.py文件中添加User-Agent列表
1
2
3
4
5
6
7
8
9
10
11
DOWNLOADER_MIDDLEWARES = {
'DoubanMeinv.middlewares.RandomUserAgent': 1,
} USER_AGENTS = [
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; AcooBrowser; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
"Mozilla/4.0 (compatible; MSIE 7.0; AOL 9.5; AOLBuild 4337.35; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
"Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.20 (KHTML, like Gecko) Chrome/19.0.1036.7 Safari/535.20",
]
修改middlewares.py文件添加如下代码:
1
2
3
4
5
6
7
8
9
10
11
12
import random

class RandomUserAgent(object):
def __init__(self, agents):
self.agents = agents @classmethod
def from_crawler(cls, crawler):
return cls(crawler.settings.getlist('USER_AGENTS')) def process_request(self, request, spider):
request.headers.setdefault('User-Agent', random.choice(self.agents))

7.禁用Cookie+设置请求延迟

某些网站可能会根据cookie来分析爬取的轨迹,为了被ban,我们最好也禁用掉cookie;同时为了避免请求太频繁而造成爬虫被ban,我们还需要设置请求间隔时间,在settings.py文件中添加以下代码:
1
2
DOWNLOAD_DELAY=1
COOKIES_ENABLED=False

8.抓取图片并保存到本地

有时候我们想把抓取到的图片直接下载并保存到本地,可以用Scrapy内置的ImagesPipeline来处理,因为ImagesPipeline用到了PIL这个图片处理模块,所以我们首先需要使用pip来安装Pillow
安装成功后,在pipelines.py代码中添加以下代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from scrapy.pipelines.images import ImagesPipeline
from scrapy import Request
import json class ImageCachePipeline(ImagesPipeline):
def get_media_requests(self, item, info):
pics = item['pics']
list = json.loads(pics)
for image_url in list:
yield Request(image_url) def item_completed(self, results, item, info):
image_paths=[x['path'] for ok,x in results if ok]
if not image_paths:
print "图片未下载好:%s" % image_paths
raise DropItem('图片未下载好 %s'%image_paths)
ImagesPipeline类有一个get_media_requests方法来进行下载的控制,所以我们在这里解析imgUrl并发起进行一个Request,在下载完成之后,会把结果传递到item_completed方法,包括 下载是否成功( True or False) 以及下载下来保存的路径和下载的路径,这里改写这个方法让他把下载失败的(Flase)的图片的路径输出出来
接下来在settings.py里设置下载图片的文件目录并启用ImageCachePipeline
1
2
3
4
5
6
7
8
#设置图片保存到本地的地址和过期时间
IMAGES_STORE='/Users/chen/Pictures/Meizi'
IMAGES_EXPIRES = 90 ITEM_PIPELINES = {
'DoubanMeinv.pipelines.DoubanmeinvPipeline': 300,
'DoubanMeinv.pipelines.ImageCachePipeline': 500,
}
等待爬虫执行完之后去IMAGES_STORE路径下查看图片就是了

9.自动运行爬虫

为了源源不断获取数据,可通过命令让爬虫每天都运行来抓取数据
1
2
3
4
// 为当前用户新增任务
crontab -e
// 增加如下记录 注意替换自己的爬虫目录 由于环境变量的原因,scrapy要给出全路径
0 10 * * * cd /home/chen/pyWork/DoubanMeinvScrapy && /usr/bin/scrapy crawl dbmeinv
上面的命令添加了一个任务,这个任务会每天早上10:00启动,这个任务要做得就是进入爬虫目录,并启动爬虫。
如果你不知道自己的scrapy的全路径,可以用终端下用which scrapy来查看

最后秀一下抓取到的数据:

Scrapy爬虫笔记的更多相关文章

  1. scrapy爬虫笔记(一)------环境配置

    前言: 本系列文章是对爬虫的简单介绍,以及教你如何用简单的方法爬取网站上的内容. 需要阅读者对html语言及python语言有基本的了解. (本系列文章也是我在学习爬虫过程中的学习笔记,随着学习的深入 ...

  2. scrapy爬虫笔记(三)------写入源文件的爬取

    开始爬取网页:(2)写入源文件的爬取 为了使代码易于修改,更清晰高效的爬取网页,我们将代码写入源文件进行爬取. 主要分为以下几个步骤: 一.使用scrapy创建爬虫框架: 二.修改并编写源代码,确定我 ...

  3. scrapy爬虫笔记(二)------交互式爬取

    开始网页爬取:(1)交互式爬取 首先,我们使用scrapy建立起爬虫的框架.在命令行中输入 scrapy shell “url” 如:scrapy shell “http://www.baidu.co ...

  4. Scrapy爬虫笔记 - 爬取知乎

    cookie是一种本地存储机制,cookie是存储在本地的 session其实就是将用户信息用户名.密码等)加密成一串字符串,返回给浏览器,以后浏览器每次请求都带着这个sessionId 状态码一般是 ...

  5. scrapy爬虫框架学习笔记(一)

    scrapy爬虫框架学习笔记(一) 1.安装scrapy pip install scrapy 2.新建工程: (1)打开命令行模式 (2)进入要新建工程的目录 (3)运行命令: scrapy sta ...

  6. Scrapy 爬虫框架学习笔记(未完,持续更新)

    Scrapy 爬虫框架 Scrapy 是一个用 Python 写的 Crawler Framework .它使用 Twisted 这个异步网络库来处理网络通信. Scrapy 框架的主要架构 根据它官 ...

  7. Scrapy:学习笔记(2)——Scrapy项目

    Scrapy:学习笔记(2)——Scrapy项目 1.创建项目 创建一个Scrapy项目,并将其命名为“demo” scrapy startproject demo cd demo 稍等片刻后,Scr ...

  8. scrapy 学习笔记1

    最近一段时间开始研究爬虫,后续陆续更新学习笔记 爬虫,说白了就是获取一个网页的html页面,然后从里面获取你想要的东西,复杂一点的还有: 反爬技术(人家网页不让你爬,爬虫对服务器负载很大) 爬虫框架( ...

  9. Python爬虫教程-31-创建 Scrapy 爬虫框架项目

    本篇是介绍在 Anaconda 环境下,创建 Scrapy 爬虫框架项目的步骤,且介绍比较详细 Python爬虫教程-31-创建 Scrapy 爬虫框架项目 首先说一下,本篇是在 Anaconda 环 ...

随机推荐

  1. 奇特的JavaScript连续赋值运算

    一.引子: }; a.x = a = {n:}; alert(a.x); // --> undefined 以上第二句 a.x = a = {n:2} 是一个连续赋值表达式.这个连续赋值表达式在 ...

  2. Tomcat中实现IP访问限制

    打开tomcat6\conf\server.xml文件 如果是要限制整个站点别人不能访问,则要将 <Valve className="org.apache.catalina.valve ...

  3. iTOP-4412 开发板的 GPIO 是怎么操作的?

    Exynos4412 全部的 GPIO 都有固定的地址,为了方便操作这些 GPIO.Linux 内核 在 gpio-exynos4.h 里面定义了一些 GPIO 的宏.比如: #define EXYN ...

  4. 【angularJS】三个学习angulaJS的链接

    1.官方文档:https://code.angularjs.org/1.5.7/docs/api 2.A Better Way to Learn AngularJS:https://thinkster ...

  5. 倍福TwinCAT(贝福Beckhoff)常见问题(FAQ)-人机界面快速入门 TC2

    创建最简单的静态文本,就像是label,就只需要绘制一个矩形框,然后填写Text,取消边框即可(你也可以设置自定义字体)   创建动态的文本框,就像是textbox,需要设置这个矩形框的Text为%d ...

  6. Android设置拍照或者上传本地图片

    效果例如以下: 看代码: MainActivity类中: package com.example.ceshidemo; import java.io.ByteArrayOutputStream; im ...

  7. 使用apache POI解析Excel文件

    1. Apache POI简介 Apache POI是Apache软件基金会的开放源码函式库,POI提供API给Java程式对Microsoft Office格式档案读和写的功能. 2. POI结构 ...

  8. svn your working copy appears to be locked run cleanup to amend the situation

    cleanup  则解决

  9. Python3 range()函数

    Python3 range() 函数用法  Python3 内置函数 Python3 range() 函数返回的是一个可迭代对象(类型是对象),而不是列表类型, 所以打印的时候不会打印列表. Pyth ...

  10. 电脑端的全能扫描王:图片转文字识别、识别pdf、图片中的文字,图片提取txt

    手机中有全能扫描王,但PC端没有.所以需要另外找. 发现微软的oneNode有提供类似的功能. 第一步.下载Microsoft OneNode http://www.onenote.com/downl ...