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爬虫笔记

    目录 机器人协议 下载速度 请求头 ITEM_PIPELINES pipelines.py JsonItemExpoter 和 JsonLinesItemExpoter start.py Github ...

  2. Python 网络爬虫笔记11 -- Scrapy 实战

    Python 网络爬虫笔记11 – Scrapy 实战 Python 网络爬虫系列笔记是笔者在学习嵩天老师的<Python网络爬虫与信息提取>课程及笔者实践网络爬虫的笔记. 课程链接:Py ...

  3. Python 网络爬虫笔记10 -- Scrapy 使用入门

    Python 网络爬虫笔记10 – Scrapy 使用入门 Python 网络爬虫系列笔记是笔者在学习嵩天老师的<Python网络爬虫与信息提取>课程及笔者实践网络爬虫的笔记. 课程链接: ...

  4. Python 网络爬虫笔记9 -- Scrapy爬虫框架

    Python 网络爬虫笔记9 – Scrapy爬虫框架 Python 网络爬虫系列笔记是笔者在学习嵩天老师的<Python网络爬虫与信息提取>课程及笔者实践网络爬虫的笔记. 课程链接:Py ...

  5. scrapy+招聘网站爬虫笔记

    scrapy+招聘网站爬虫笔记 先看看要爬的网站:https://sou.zhaopin.com/?jl=719&kw=%E8%8D%AF%E7%89%A9 目的:获取每个城市的时间.区域.城 ...

  6. python爬虫笔记——Scrapy框架(浅学)

    一.创建Scrapy爬虫项目 步骤: 安装scrapy:在pycharm项目(自己新建的爬虫项目)的终端输入 pip install scrapy 创建爬虫项目:同样在终端输入 scrapy star ...

  7. Python 网络爬虫笔记8 -- 股票数据定向爬虫

    Python 网络爬虫笔记8 – 股票数据定向爬虫 Python 网络爬虫系列笔记是笔者在学习嵩天老师的<Python网络爬虫与信息提取>课程及笔者实践网络爬虫的笔记. 课程链接:Pyth ...

  8. Python 网络爬虫笔记6 -- 正则表达式

    Python 网络爬虫笔记6 – 正则表达式 Python 网络爬虫系列笔记是笔者在学习嵩天老师的<Python网络爬虫与信息提取>课程及笔者实践网络爬虫的笔记. 课程链接:Python网 ...

  9. Python 网络爬虫笔记5 -- Beautiful Soup库实战

    Python 网络爬虫笔记5 – Beautiful Soup库实战 Python 网络爬虫系列笔记是笔者在学习嵩天老师的<Python网络爬虫与信息提取>课程及笔者实践网络爬虫的笔记. ...

最新文章

  1. 一行代码搞定 Python 逐行内存消耗分析
  2. weblogic登录验证被拒绝_使用Kubernetes身份在微服务之间进行身份验证
  3. 流行的14个机器学习编程语言框架和工具
  4. require和require_once的区别
  5. vxworks操作系统_【7.10开播】最新自主研发工业操作系统发布会行业top来助阵,邀您共同见证(附报名)...
  6. JVM—垃圾回收与算法
  7. 一个Demo让你掌握Android所有控件
  8. c mysql 免安装版_MySQL5.6免安装版环境配置图文教程
  9. 什么是pisa测试_PISA测试排名世界第一,中国教育已是世界冠军?
  10. display:inline-block带来的问题及解决办法
  11. vue设置页面滚动高度_vue中获取滚动高度或指定滚动到某位置
  12. JavaScipt屏蔽浏览器右上角“最小化,最大化,关闭”
  13. 操作系统课程设计 —— 模拟磁盘文件系统实现 (Java)
  14. 2017高教杯数学建模B 题分析
  15. dw怎么在框架中加入网页_Dreamweaver如何用框架建立网站
  16. 局域网是计算机硬件和什么结合的,2017年计算机硬件知识备考试题及答案
  17. Bugku 分析 特殊后门(wireshark流量包分析)
  18. 程序员的佛系炒股日常
  19. iPhoneX利用unc0ver来越狱iOS12
  20. template和template

热门文章

  1. 热议 | 深圳中学教师年薪35万,有一半是博士
  2. 专利案件管理软件 唯德系统
  3. 与AI人才有关的讨论
  4. java数据导出ex_Java高级特性注解:注解实现Excel导出功能
  5. PHP初学者头疼问题总结
  6. 网信办:从严整治激情打赏、高额打赏、诱导打赏
  7. 小红书再度出拳整治医美 首批处置违规笔记27.9万篇
  8. 三星Galaxy S22系列发布会准确时间曝光:2月9日正式揭晓
  9. 母亲节:微信喊你给母亲充钱 华为帮你教爸妈用手机
  10. 双十一最具性价比机型,Redmi K30S至尊纪念版上手体验