Scrapy爬虫笔记
来源:互联网 发布:java与xml 第3版 pdf 编辑:程序博客网 时间:2024/06/15 22:33
Scrapy是一个优秀的Python爬虫框架,可以很方便的爬取web站点的信息供我们分析和挖掘,在这记录下最近使用的一些心得。
1.安装
通过pip或者easy_install安装:
1
sudo pip install scrapy
2.创建爬虫项目
1
scrapy startproject youProjectName
3.抓取数据
首先在items.py里定义要抓取的内容,以豆瓣美女为例:
1234567891011121314151617
from scrapy.item import Field,Itemclass 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 cmdlinecmdline.execute('scrapy crawl amazon_products -o items.csv -t csv'.split())#-o 代表输出文件 -t 代表文件格式
接着编辑爬虫文件,实例如下:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
# -*- coding: utf-8 -*-import scrapyimport refrom DoubanMeinv.items import DoubanmeinvItem,UserItemimport jsonimport timefrom datetime import datetimefrom scrapy.exceptions import CloseSpiderimport sysreload(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数据库的所需信息,如下所示:
12345678910
DB_SERVER = 'MySQLdb'DB_CONNECT = { 'host' : 'localhost', 'user' : 'root', 'passwd' : '', 'port' : 3306, 'db' :'dbMeizi', 'charset' : 'utf8', 'use_unicode' : True}
然后编辑pipelines.py文件,添加代码如下:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
from scrapy.conf import settingsfrom scrapy.exceptions import DropItemfrom twisted.enterprise import adbapiimport jsonclass 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
1234
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列表
1234567891011
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文件添加如下代码:
123456789101112
import randomclass 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文件中添加以下代码:
12
DOWNLOAD_DELAY=1COOKIES_ENABLED=False
8.抓取图片并保存到本地
有时候我们想把抓取到的图片直接下载并保存到本地,可以用Scrapy内置的ImagesPipeline
来处理,因为ImagesPipeline
用到了PIL这个图片处理模块,所以我们首先需要使用pip来安装Pillow
安装成功后,在pipelines.py代码中添加以下代码:
12345678910111213141516
from scrapy.pipelines.images import ImagesPipelinefrom scrapy import Requestimport jsonclass 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
12345678
#设置图片保存到本地的地址和过期时间IMAGES_STORE='/Users/chen/Pictures/Meizi'IMAGES_EXPIRES = 90ITEM_PIPELINES = { 'DoubanMeinv.pipelines.DoubanmeinvPipeline': 300, 'DoubanMeinv.pipelines.ImageCachePipeline': 500,}
等待爬虫执行完之后去IMAGES_STORE路径下查看图片就是了
9.自动运行爬虫
为了源源不断获取数据,可通过命令让爬虫每天都运行来抓取数据
1234
// 为当前用户新增任务crontab -e// 增加如下记录 注意替换自己的爬虫目录 由于环境变量的原因,scrapy要给出全路径0 10 * * * cd /home/chen/pyWork/DoubanMeinvScrapy && /usr/bin/scrapy crawl dbmeinv
上面的命令添加了一个任务,这个任务会每天早上10:00启动,这个任务要做得就是进入爬虫目录,并启动爬虫。
如果你不知道自己的scrapy的全路径,可以用终端下用which scrapy
来查看
阅读全文
0 0
- Scrapy爬虫框架笔记
- Scrapy爬虫笔记-未完成
- Scrapy爬虫笔记
- Scrapy爬虫笔记
- Scrapy爬虫笔记【4-Scrapy命令行】
- Scrapy爬虫笔记【3-XPaths】
- 爬虫学习笔记-Scrapy初识
- 爬虫学习笔记-Scrapy散记
- Scrapy爬虫笔记【7-Scrapy核心知识基础】
- python爬虫框架scrapy学习笔记
- Scrapy爬虫笔记【1-基本框架】
- Scrapy爬虫笔记【2-基本流程】
- python爬虫框架scrapy学习笔记
- Scrapy笔记(10)- 动态配置爬虫
- python爬虫笔记 --------scrapy框架(1)
- python爬虫笔记 --------scrapy框架(2)
- python爬虫笔记 --------scrapy框架(3)
- python爬虫笔记 --------scrapy框架(4)
- Http请求中的Context-Type及其SpringMVC中的使用
- zookeeper的日志文件和快照的可视化
- 组合数取模
- 第四周项目3
- 集成学习之Adaboost算法原理小结
- Scrapy爬虫笔记
- 2017 icpc 南宁赛区 F.Overlapping Rectangles(重叠矩形的最大面积+线段树模板)
- java 技术资料,jar包下载,工具下载等资源网站
- HDOJ2009(预处理)
- css background-image 自适应宽高
- Hive基础(3):表分类、视图、数据加载方式、数据的导出、本地模式
- 梯度提升树(GBDT)原理
- jquery.ajax 跨域请求webapi,设置headers
- 实战演练 test16 T2