赞
踩
1. Scrapy框架介绍与安装
2. Scrapy框架的使用
3. Selector选择器
4. Spider的使用
5. Downloader Middleware的使用
6. Spider Middleware的使用
7. ItemPipeline的使用
8. Scrapy实战案例
Scrapy
是: 由Python
语言开发的一个快速、高层次的屏幕抓取和web抓取框架,用于抓取web站点并从页面中提取结构化的数据。数据挖掘
、监测
和自动化测试
。]
]
数据处理流程
$ pip install scrapy
F:\Python02\demo>scrapy version
Scrapy 1.5.0
$ pip download scrapy -d ./
# 通过指定国内镜像源下载
$pip download -i https://pypi.tuna.tsinghua.edu.cn/simple scrapy -d ./
]
$ pip install Scrapy-1.5.0-py2.py3-none-any.whl
Microsoft Visual C++ 14.0
那么请先安装此软件后再执行上面的命令安装就可以了。]
]
]
全局命令
和 项目命令
。全局命令
:在哪里都能使用。项目命令
:必须在爬虫项目里面才能使用。C:\Users\AOBO>scrapy -h Scrapy 1.2.1 - no active project 使用格式: scrapy <command> [options] [args] 可用的命令: bench 测试本地硬件性能(工作原理:):scrapy bench commands fetch 取URL使用Scrapy下载 genspider 产生新的蜘蛛使用预先定义的模板 runspider 运用单独一个爬虫文件:scrapy runspider abc.py settings 获取设置值 shell 进入交互终端,用于爬虫的调试(如果你不调试,那么就不常用):scrapy shell http://www.baidu.com --nolog(--nolog 不显示日志信息) startproject 创建一个爬虫项目,如:scrapy startproject demo(demo 创建的爬虫项目的名字) version 查看版本:(scrapy version) view 下载一个网页的源代码,并在默认的文本编辑器中打开这个源代码:scrapy view http://www.aobossir.com/ [ more ] 从项目目录运行时可获得更多命令 使用 "scrapy <command> -h" 要查看有关命令的更多信息
D:\BaiduYunDownload\first>scrapy -h Scrapy 1.2.1 - project: first Usage: scrapy <command> [options] [args] Available commands: bench Run quick benchmark test check Check spider contracts commands crawl 运行一个爬虫文件。:scrapy crawl f1 或者 scrapy crawl f1 --nolog edit 使用编辑器打开爬虫文件 (Windows上似乎有问题,Linux上没有问题):scrapy edit f1 fetch Fetch a URL using the Scrapy downloader genspider Generate new spider using pre-defined templates list 列出当前爬虫项目下所有的爬虫文件: scrapy list parse Parse URL (using its spider) and print the results runspider Run a self-contained spider (without creating a project) settings 获取设置值 shell 进入交互终端,用于爬虫的调试(如果你不调试,那么就不常用) startproject 创建一个爬虫项目,如:scrapy startproject demo(demo 创建的爬虫项目的名字) version 查看版本:(scrapy version) view 下载一个网页的源代码,并在默认的文本编辑器中打开这个源代码 Use "scrapy <command> -h" to see more info about a command
注意:Scrapy运行ImportError: No module named win32api错误。请安装:pip install pypiwin32
查看所有命令
scrapy -h
查看帮助信息:
scapy --help
查看版本信息:
(venv)ql@ql:~$ scrapy version
Scrapy 1.1.2
(venv)ql@ql:~$
(venv)ql@ql:~$ scrapy version -v
Scrapy : 1.1.2
lxml : 3.6.4.0
libxml2 : 2.9.4
Twisted : 16.4.0
Python : 2.7.12 (default, Jul 1 2016, 15:12:24) - [GCC 5.4.0 20160609]
pyOpenSSL : 16.1.0 (OpenSSL 1.0.2g-fips 1 Mar 2016)
Platform : Linux-4.4.0-36-generic-x86_64-with-Ubuntu-16.04-xenial
(venv)ql@ql:~$
新建一个工程
scrapy startproject spider_name
构建爬虫genspider(generator spider)
一个工程中可以存在多个spider, 但是名字必须唯一
scrapy genspider name domain
#如:
#scrapy genspider sohu sohu.org
查看当前项目内有多少爬虫
scrapy list
view使用浏览器打开网页
scrapy view http://www.baidu.com
# 进入该url的交互环境
scrapy shell http://www.dmoz.org/Computers/Programming/Languages/Python/Books/
之后便进入交互环境,我们主要使用这里面的response命令, 例如可以使用
response.xpath() #括号里直接加xpath路径
runspider命令用于直接运行创建的爬虫, 并不会运行整个项目
scrapy runspider 爬虫名称
scrapy startproject demo
demo
├── demo
│ ├── __init__.py
│ ├── __pycache__
│ ├── items.py # Items的定义,定义抓取的数据结构
│ ├── middlewares.py # 定义Spider和DownLoader的Middlewares中间件实现。
│ ├── pipelines.py # 它定义Item Pipeline的实现,即定义数据管道
│ ├── settings.py # 它定义项目的全局配置
│ └── spiders # 其中包含一个个Spider的实现,每个Spider都有一个文件
│ ├── __init__.py
│ └── __pycache__
└── scrapy.cfg #Scrapy部署时的配置文件,定义了配置文件路径、部署相关信息等内容
scrapy genspider fang fang.5i5j.com $ tree ├── demo │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-36.pyc │ │ └── settings.cpython-36.pyc │ ├── items.py │ ├── middlewares.py │ ├── pipelines.py │ ├── settings.py │ └── spiders │ ├── __init__.py │ ├── __pycache__ │ │ └── __init__.cpython-36.pyc │ └── fang.py #在spiders目录下有了一个爬虫类文件fang.py └── scrapy.cfg # fang.py的文件代码如下: # -*- coding: utf-8 -*- import scrapy class FangSpider(scrapy.Spider): name = 'fang' allowed_domains = ['fang.5i5j.com'] start_urls = ['http://fang.5i5j.com/'] def parse(self, response): pass
import scrapy
class FangItem(scrapy.Item):
# define the fields for your item here like:
title = scrapy.Field()
address = scrapy.Field()
time = scrapy.Field()
clicks = scrapy.Field()
price = scrapy.Field()
#pass
# -*- coding: utf-8 -*- import scrapy from demo.items import FangItem class FangSpider(scrapy.Spider): name = 'fang' allowed_domains = ['fang.5i5j.com'] #start_urls = ['http://fang.5i5j.com/'] start_urls = ['https://fang.5i5j.com/bj/loupan/'] def parse(self, response): hlist = response.css("div.houseList_list") for vo in hlist: item = FangItem() item['title'] = vo.css("h3.fontS20 a::text").extract_first() item['address'] = vo.css("span.addressName::text").extract_first() item['time'] = vo.re("<span>(.*?)开盘</span>")[0] item['clicks'] = vo.re("<span><i>([0-9]+)</i>浏览</span>")[0] item['price'] = vo.css("i.fontS24::text").extract_first() #print(item) yield item
class DemoPipeline(object):
def process_item(self, item, spider):
print(item)
return item
执行如下命令来启用数据爬取
scrapy crawl fang
将结果保存到文件中: 格式:json、csv、xml、pickle、marshal等
scrapy crawl fang -o fangs.json
scrapy crawl fang -o fangs.csv
scrapy crawl fang -o fangs.xml
scrapy crawl fang -o fangs.pickle
scrapy crawl fang -o fangs.marshal
FormRequest
来完成POST提交,并可以携带参数。http://fanyi.youdao.com/translate?smartresult=dict&smartresult=rule
youdao
有道的爬虫文件:scrapy genspider youdao fanyi.youdao.com
# -*- coding: utf-8 -*- import scrapy,json class YoudaoSpider(scrapy.Spider): name = 'youdao' allowed_domains = ['fanyi.youdao.com'] #start_urls = ['http://fanyi.youdao.com'] def start_requests(self): url = 'http://fanyi.youdao.com/translate?smartresult=dict&smartresult=rule' keyword = input("请输入要翻译的单词:") data = {'i':keyword,'doctype': 'json',} # FormRequest 是Scrapy发送POST请求的方法 yield scrapy.FormRequest( url = url, formdata = data, callback = self.parse ) def parse(self, response): res = json.loads(response.body) print(res['translateResult'][0][0]['tgt']) input("按任意键继续")
from scrapy import Selector
content="<html><head><title>My html</title><body><h3>Hello Word!</h3></body></head></html>"
selector = Selector(text=content)
print(selector.xpath('/html/head/title/text()').extract_first())
print(selector.css('h3::text').extract_first())
zhangtaodeMacBook-Pro:scrapydemo zhangtao$ scrapy shell http://www.baidu.com 2018-05-08 14:46:29 [scrapy.utils.log] INFO: Scrapy 1.5.0 started (bot: scrapybot) 2018-05-08 14:46:29 [scrapy.utils.log] INFO: Versions: lxml 4.2.1.0, libxml2 2.9.8, cssselect 1.0.3, parsel 1.4.0, w3lib 1.19.0, Twisted 18.4.0, Python 3.6.4 (default, Jan 6 2018, 11:49:38) - [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)], pyOpenSSL 17.5.0 (OpenSSL 1.1.0h 27 Mar 2018), cryptography 2.2.2, Platform Darwin-15.6.0-x86_64-i386-64bit 2018-05-08 14:46:29 [scrapy.crawler] INFO: Overridden settings: {'DUPEFILTER_CLASS': 'scrapy.dupefilters.BaseDupeFilter', 'LOGSTATS_INTERVAL': 0} 2018-05-08 14:46:29 [scrapy.middleware] INFO: Enabled extensions: ['scrapy.extensions.corestats.CoreStats', 'scrapy.extensions.telnet.TelnetConsole', 'scrapy.extensions.memusage.MemoryUsage'] 2018-05-08 14:46:29 [scrapy.middleware] INFO: Enabled downloader middlewares: ['scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware', 'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware', 'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware', 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware', 'scrapy.downloadermiddlewares.retry.RetryMiddleware', 'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware', 'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware', 'scrapy.downloadermiddlewares.redirect.RedirectMiddleware', 'scrapy.downloadermiddlewares.cookies.CookiesMiddleware', 'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware', 'scrapy.downloadermiddlewares.stats.DownloaderStats'] 2018-05-08 14:46:29 [scrapy.middleware] INFO: Enabled spider middlewares: ['scrapy.spidermiddlewares.httperror.HttpErrorMiddleware', 'scrapy.spidermiddlewares.offsite.OffsiteMiddleware', 'scrapy.spidermiddlewares.referer.RefererMiddleware', 'scrapy.spidermiddlewares.urllength.UrlLengthMiddleware', 'scrapy.spidermiddlewares.depth.DepthMiddleware'] 2018-05-08 14:46:29 [scrapy.middleware] INFO: Enabled item pipelines: [] 2018-05-08 14:46:29 [scrapy.extensions.telnet] DEBUG: Telnet console listening on 127.0.0.1:6023 2018-05-08 14:46:29 [scrapy.core.engine] INFO: Spider opened 2018-05-08 14:46:29 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://www.baidu.com> (referer: None) [s] Available Scrapy objects: [s] scrapy scrapy module (contains scrapy.Request, scrapy.Selector, etc) [s] crawler <scrapy.crawler.Crawler object at 0x108ea8ac8> [s] item {} [s] request <GET http://www.baidu.com> [s] response <200 http://www.baidu.com> [s] settings <scrapy.settings.Settings object at 0x109cbb8d0> [s] spider <DefaultSpider 'default' at 0x109f56e10> [s] Useful shortcuts: [s] fetch(url[, redirect=True]) Fetch URL and update local objects (by default, redirects are followed) [s] fetch(req) Fetch a scrapy.Request and update local objects [s] shelp() Shell help (print this help) [s] view(response) View response in a browser >>> response.url 'http://www.baidu.com' >>> response.status 200 >>> response.xpath('/html/head/title/text()').extract_first() '百度一下,你就知道' >>> response.xpath('//a/text()').extract_first() '新闻' >>> response.xpath('//a/text()').extract() ['新闻', 'hao123', '地图', '视频', '贴吧', '登录', '更多产品', '关于百度', 'About Baidu', '使用百度前必读', '意见反馈'] >>> response.xpath('//a/@href').extract() ['http://news.baidu.com', 'http://www.hao123.com', 'http://map.baidu.com', 'http://v.baidu.com', 'http://tieba.baidu.com', 'http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u=http%3A%2F%2Fwww.baidu.com%2f%3fbdorz_come%3d1', '//www.baidu.com/more/', 'http://home.baidu.com', 'http://ir.baidu.com', 'http://www.baidu.com/duty/', 'http://jianyi.baidu.com/']
$ scrapy shell https://www.taobao.com/tbhome/page/special-markets ... ... >>> response.url 'https://www.taobao.com/tbhome/page/special-markets' >>> response.status 200 >>> response.selector.xpath("//dt/text()") [<Selector xpath='//dt/text()' data='时尚爆料王'>, <Selector xpath='//dt/text()' data='品质生活家'>, <Selector xpath='//dt/text()' data='特色玩味控'>, <Selector xpath='//dt/text()' data='实惠专业户'>] >>> response.selector.xpath("//dt/text()").extract() ['时尚爆料王', '品质生活家', '特色玩味控', '实惠专业户'] >>> dllist = response.selector.xpath("//dl[@class='market-list']") >>> for v in dllist: ... print(v.xpath("./dt/text()").extract_first()) ... 时尚爆料王 品质生活家 特色玩味控 实惠专业户 >>> for v in dllist: ... print(v.xpath("./dt/text()").extract_first()) ... print("="*50) ... alist = v.xpath(".//a") ... for a in alist: ... print(a.xpath("./@href").extract_first(),end=":") ... print(a.xpath("./span/img/@alt").extract_first()) ... 时尚爆料王 ================================================== https://if.taobao.com/:潮流从这里开始 https://guang.taobao.com/:外貌协会の逛街指南 https://mei.taobao.com/:妆 出你的腔调 https://g.taobao.com/:探索全球美好生活 //star.taobao.com/:全球明星在这里 https://mm.taobao.com/:美女红人集中地 https://www.taobao.com/markets/designer/stylish:全球创意设计师平台 品质生活家 ================================================== https://chi.taobao.com/chi/:食尚全球 地道中国 //q.taobao.com:懂得好生活 https://www.jiyoujia.com/:过我想要的生活 https://www.taobao.com/markets/sph/sph/sy:尖货奢品品味选择 https://www.taobao.com/markets/qbb/index:享受育儿生活新方式 //car.taobao.com/:买车省钱,用车省心 //sport.taobao.com/:爱上运动每一天 //zj.taobao.com:匠心所在 物有所值 //wt.taobao.com/:畅享优质通信生活 https://www.alitrip.com/:比梦想走更远 特色玩味控 ================================================== https://china.taobao.com:地道才够味! https://www.taobao.com/markets/3c/tbdc:为你开启潮流新生活 https://acg.taobao.com/:ACGN 好玩好看 https://izhongchou.taobao.com/index.htm:认真对待每一个梦想。 //enjoy.taobao.com/:园艺宠物爱好者集中营 https://sf.taobao.com/:法院处置资产,0佣金捡漏 https://zc-paimai.taobao.com/:超值资产,投资首选 https://paimai.taobao.com/:想淘宝上拍卖 //xue.taobao.com/:给你未来的学习体验 //2.taobao.com:让你的闲置游起来 https://ny.taobao.com/:价格实惠品类齐全 实惠专业户 ================================================== //tejia.taobao.com/:优质好货 特价专区 https://qing.taobao.com/:品牌尾货365天最低价 https://ju.taobao.com/jusp/other/mingpin/tp.htm:奢侈品团购第一站 https://ju.taobao.com/jusp/other/juliangfan/tp.htm?spm=608.5847457.102202.5.jO4uZI:重新定义家庭生活方式 https://qiang.taobao.com/:抢到就是赚到! https://ju.taobao.com/jusp/nv/fcdppc/tp.htm:大牌正品 底价特惠 https://ju.taobao.com/jusp/shh/life/tp.htm?:惠聚身边精选好货 https://ju.taobao.com/jusp/sp/global/tp.htm?spm=0.0.0.0.biIDGB:10点上新 全球底价 https://try.taobao.com/index.htm:总有新奇等你发现
>>> response.url 'https://www.taobao.com/tbhome/page/market-list' >>> response.status 200 >>> response.css("a.category-name-level1::text").extract() ['女装男装', '鞋类箱包', '母婴用品', '护肤彩妆', '汇吃美食', '珠宝配饰', '家装建材', '家居家纺', '百货市场', '汽车·用品', '手机数码', '家电办公', '更多服务', '生活服务', '运动户外', '花鸟文娱', '农资采购'] #获取淘宝页面中所有分类信息 >>> dlist = response.css("div.home-category-list") >>> for dd in dlist: print(dd.css("a.category-name-level1::text").extract_first()) print("="*50) alist = dd.css("li.category-list-item") for v in alist: print(v.xpath("./a/text()").extract_first()) print("-"*50) talist = v.css("div.category-items a") for a in talist: print(a.css("::text").extract_first(),end=" ") print() >>>女装男装 ================================================== 潮流女装 -------------------------------------------------- 羽绒服 毛呢大衣 毛衣 冬季外套 新品 裤子 连衣裙 腔调 时尚男装 -------------------------------------------------- 秋冬新品 淘特莱斯 淘先生 拾货 秋冬外套 时尚套装 潮牌 爸爸装 性感内衣 -------------------------------------------------- 春新品 性感诱惑 甜美清新 简约优雅 奢华高贵 运动风 塑身 基础内衣 ... 注:css中获取属性:a.css("::attr(href)").extract_first()
>>> response.xpath("//head").re("<title>(.*?)</title>")
['淘宝首页行业市场']
>>> response.selector.re("<a .*?>(.*?)</a>")
定义抓取网站的动作
和分析爬取下来的网页
。Python36/Lib/site-packages/scrapy/spiders/__init__.py
import logging import warnings from scrapy import signals from scrapy.http import Request from scrapy.utils.trackref import object_ref from scrapy.utils.url import url_is_from_spider from scrapy.utils.deprecate import create_deprecated_class from scrapy.exceptions import ScrapyDeprecationWarning from scrapy.utils.deprecate import method_is_overridden #所有爬虫的基类,自定义的爬虫必须从继承此类 class Spider(object_ref): #定义spider名字的字符串(string)。spider的名字定义了Scrapy如何定位(并初始化)spider,所以其必须是唯一的。 #name是spider最重要的属性,而且是必须的。 #一般做法是以该网站(domain)(加或不加 后缀 )来命名spider。 例如,如果spider爬取 douban.com ,该spider通常会被命名为 douban name = None custom_settings = None #初始化,提取爬虫名字,start_ruls def __init__(self, name=None, **kwargs): if name is not None: self.name = name # 如果爬虫没有名字,中断后续操作则报错 elif not getattr(self, 'name', None): raise ValueError("%s must have a name" % type(self).__name__) # python 对象或类型通过内置成员__dict__来存储成员信息 self.__dict__.update(kwargs) #URL列表。当没有指定的URL时,spider将从该列表中开始进行爬取。因此,第一个被获取到的页面的URL将是该列表之一。 后续的URL将会从获取到的数据中提取。 if not hasattr(self, 'start_urls'): self.start_urls = [] @property def logger(self): logger = logging.getLogger(self.name) return logging.LoggerAdapter(logger, {'spider': self}) # 打印Scrapy执行后的log信息 def log(self, message, level=log.DEBUG, **kw): log.msg(message, spider=self, level=level, **kw) @classmethod def from_crawler(cls, crawler, *args, **kwargs): spider = cls(*args, **kwargs) spider._set_crawler(crawler) return spider #判断对象object的属性是否存在,不存在做断言处理 def set_crawler(self, crawler): assert not hasattr(self, '_crawler'), "Spider already bounded to %s" % crawler self._set_crawler(crawler) def _set_crawler(self, crawler): self.crawler = crawler self.settings = crawler.settings crawler.signals.connect(self.close, signals.spider_closed) #@property #def crawler(self): # assert hasattr(self, '_crawler'), "Spider not bounded to any crawler" # return self._crawler #@property #def settings(self): # return self.crawler.settings #该方法将读取start_urls内的地址,并为每一个地址生成一个Request对象,交给Scrapy下载并返回Response #该方法仅调用一次 def start_requests(self): for url in self.start_urls: yield self.make_requests_from_url(url) #start_requests()中调用,实际生成Request的函数。 #Request对象默认的回调函数为parse(),提交的方式为get def make_requests_from_url(self, url): return Request(url, dont_filter=True) #默认的Request对象回调函数,处理返回的response。 #生成Item或者Request对象。用户必须实现这个类 def parse(self, response): raise NotImplementedError @classmethod def handles_request(cls, request): return url_is_from_spider(request.url, cls) @staticmethod def close(spider, reason): closed = getattr(spider, 'closed', None) if callable(closed): return closed(reason) def __str__(self): return "<%s %r at 0x%0x>" % (type(self).__name__, self.name, id(self)) __repr__ = __str__
Spider
类继承自scrapy.spiders.Spider
.
Spider
类这个提供了start_requests()
方法的默认实现,读取并请求start_urls
属性,并调用parse()
方法解析结果。
Spider
类的属性和方法:
name
:爬虫名称,必须唯一的,可以生成多个相同的Spider实例,数量没有限制。allowed_domains
: 允许爬取的域名,是可选配置,不在此范围的链接不会被跟进爬取。start_urls
: 它是起始URL列表,当我们没有实现start_requests()方法时,默认会从这个列表开始抓取。custom_settings
: 它是一个字典,专属于Spider的配置,此设置会覆盖项目全局的设置,必须定义成类变量。crawler
:它是由from_crawler()方法设置的,Crawler对象包含了很多项目组件,可以获取settings等配置信息。settings
: 利用它我们可以直接获取项目的全局设置变量。start_requests()
: 使用start_urls里面的URL来构造Request,而且Request是GET请求方法。parse()
: 当Response没有指定回调函数时,该方法会默认被调用。closed()
: 当Spider关闭时,该方法会调用。python
信息的百度文库搜索信息(每页10条信息)https://wenku.baidu.com/search?word=python&pn=0
第一页https://wenku.baidu.com/search?word=python&pn=10
第二页$ scrapy startproject bdwenku
$ cd bdwenku
$ scrapy genspider wenku wenku.baidu.com
wenku.py
爬虫文件,代码如下:# -*- coding: utf-8 -*-
import scrapy
class WenkuSpider(scrapy.Spider):
name = 'wenku'
allowed_domains = ['wenku.baidu.com']
start_urls = ['https://wenku.baidu.com/search?word=python&pn=0']
def parse(self, response):
print("Hello Scrapy")
print(response)
$ scrapy crawl wenku
DEBUG: Forbidden by robots.txt:
请求被拒绝了...
2018-05-11 11:00:54 [scrapy.core.engine] INFO: Spider opened
2018-05-11 11:00:54 [scrapy.extensions.logstats] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2018-05-11 11:00:54 [scrapy.extensions.telnet] DEBUG: Telnet console listening on 127.0.0.1:6027
2018-05-11 11:00:56 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://wenku.baidu.com/robots.txt> (referer: None)
2018-05-11 11:00:56 [scrapy.downloadermiddlewares.robotstxt] DEBUG: Forbidden by robots.txt: <GET https://wenku.baidu.com/search?word=python&pn=0>
2018-05-11 11:00:56 [scrapy.core.engine] INFO: Closing spider (finished)
2018-05-11 11:00:56 [scrapy.statscollectors] INFO: Dumping Scrapy stats:
{'downloader/exception_count': 1,
'downloader/exception_type_count/scrapy.exceptions.IgnoreRequest': 1,
...
wenku.py
的几种写法:# 单请求的信息爬取 # -*- coding: utf-8 -*- import scrapy class WenkuSpider(scrapy.Spider): name = 'wenku' allowed_domains = ['wenku.baidu.com'] start_urls = ['https://wenku.baidu.com/search?word=python&pn=0'] def parse(self, response): dllist = response.selector.xpath("//dl") #print(len(dllist)) for dd in dllist: print(dd.xpath("./dt/p/a/@title").extract_first()) # 多个请求的信息爬取 # -*- coding: utf-8 -*- import scrapy class WenkuSpider(scrapy.Spider): name = 'wenku' allowed_domains = ['wenku.baidu.com'] start_urls = ['https://wenku.baidu.com/search?word=python&pn=0','https://wenku.baidu.com/search?word=python&pn=10','https://wenku.baidu.com/search?word=python&pn=20'] def parse(self, response): dllist = response.selector.xpath("//dl") #print(len(dllist)) for dd in dllist: print(dd.xpath("./dt/p/a/@title").extract_first()) print("="*70) #输出一条每个请求后分割线 # 实现一个爬取循环,获取10页信息 # -*- coding: utf-8 -*- import scrapy class WenkuSpider(scrapy.Spider): name = 'wenku' allowed_domains = ['wenku.baidu.com'] start_urls = ['https://wenku.baidu.com/search?word=python&pn=0'] p=0 def parse(self, response): dllist = response.selector.xpath("//dl") #print(len(dllist)) for dd in dllist: print(dd.xpath("./dt/p/a/@title").extract_first()) print("="*70) self.p += 1 if self.p < 10: next_url = 'https://wenku.baidu.com/search?word=python&pn='+str(self.p*10) url = response.urljoin(next_url) #构建绝对url地址(这里可省略) yield scrapy.Request(url=url,callback=self.parse) python python教程 PYTHON测试题 简明_Python_教程 如何自学 Python(干货合集) python python Python介绍(Introduction to Python) Python编程入门(适合于零基础朋友) Python教程 ====================================================================== python 十分钟学会Python Python 基础语法(一) python基础分享 Python入门 python新手教程 python资源中文大全 python_笔记 Python与中文处理 python ====================================================================== python python教程 PYTHON测试题 简明_Python_教程 如何自学 Python(干货合集) python python Python介绍(Introduction to Python) Python编程入门(适合于零基础朋友) Python教程 ======================================================================
# 在python3.6/site-packages/scrapy/settings/default_settings.py默认配置中 DOWNLOADER_MIDDLEWARES_BASE = { # Engine side 'scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware': 100, 'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware': 300, 'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware': 350, 'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware': 400, 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': 500, 'scrapy.downloadermiddlewares.retry.RetryMiddleware': 550, 'scrapy.downloadermiddlewares.ajaxcrawl.AjaxCrawlMiddleware': 560, 'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware': 580, 'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 590, 'scrapy.downloadermiddlewares.redirect.RedirectMiddleware': 600, 'scrapy.downloadermiddlewares.cookies.CookiesMiddleware': 700, 'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 750, 'scrapy.downloadermiddlewares.stats.DownloaderStats': 850, 'scrapy.downloadermiddlewares.httpcache.HttpCacheMiddleware': 900, # Downloader side }
None
,返回一个Response对象
、返回一个Request对象
或raise IgnoreRequest
。三种返回值的作用是不同的。None
:Scrapy将继续处理该request,执行其他的中间件的相应方法,直到合适的下载器处理函数(download handler)被调用,该request被执行(其response被下载)。Response对象
:Scrapy将不会调用任何其他的process_request()或process_exception() 方法,或相应地下载函数;其将返回该response。 已安装的中间件的 process_response() 方法则会在每个response返回时被调用。Request对象
:Scrapy则停止调用 process_request方法并重新调度返回的request。当新返回的request被执行后, 相应地中间件链将会根据下载的response被调用。raise一个IgnoreRequest异常
:则安装的下载中间件的 process_exception() 方法会被调用。如果没有任何一个方法处理该异常, 则request的errback(Request.errback)方法会被调用。如果没有代码处理抛出的异常,则该异常被忽略且不记录。response对象
,request对象
,或者raise一个IgnoreRequest异常
...
def process_response(self, request, response, spider):
response.status = 201
return response
...
# 在命令行下直接运行scrapy shell命令爬取信息,报403错误
$ scrapy shell https://book.douban.com/top250
>>> response.status
>>> 403
scrapy startproject douban
cd douban
scrapy genspider dbbook book.douban.com
# -*- coding: utf-8 -*-
import scrapy
class DbbookSpider(scrapy.Spider):
name = 'dbbook'
allowed_domains = ['book.douban.com']
start_urls = ['https://book.douban.com/top250?start=0']
def parse(self, response):
#print("状态:")
pass
$ scrapy crawl dbbook
#结果返回403错误(服务器端拒绝访问)。
原因分析:默认scrapy框架请求信息中的User-Agent
的值为:Scrapy/1.5.0(http://scrapy.org)
.
解决方案:我们可以在settings.py配置文件中:设置 USER_AGENT
或者DEFAULT_REQUEST_HEADERS
信息:
USER_AGENT = 'Opera/9.80(WindowsNT6.1;U;en)Presto/2.8.131Version/11.11'
或者
...
DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36',
}
...
DOWNLOADER_MIDDLEWARES
信息:DOWNLOADER_MIDDLEWARES = {
'douban.middlewares.DoubanDownloaderMiddleware': 543,
}
def process_request(self, request, spider):
#输出header头信息
print(request.headers)
#伪装浏览器用户
request.headers['user-agent']='Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36'
return None
SPIDER_MIDDLEWARES = {
'myproject.middlewares.CustomSpiderMiddleware': 543,
}
SPIDER_MIDDLEWARES = {
'myproject.middlewares.CustomSpiderMiddleware': 543,
'scrapy.contrib.spidermiddleware.offsite.OffsiteMiddleware': None,
}
当response通过spider中间件时,该方法被调用,处理该response。
`process_spider_input()` 应该返回 None 或者抛出一个异常。
如果其返回 None ,Scrapy将会继续处理该response,调用所有其他的中间件直到spider处理该response。
如果其跑出一个异常(exception),Scrapy将不会调用任何其他中间件的 process_spider_input() 方法,并调用request的errback。 errback的输出将会以另一个方向被重新输入到中间件链中,使用 process_spider_output() 方法来处理,当其抛出异常时则带调用 process_spider_exception() 。
参数:
response (Response 对象) – 被处理的response
spider (Spider 对象) – 该response对应的spider
当Spider处理response返回result时,该方法被调用。
`process_spider_output()` 必须返回包含 Request 或 Item 对象的可迭代对象(iterable)。
参数:
response (Response 对象) – 生成该输出的response
result (包含 Request 或 Item 对象的可迭代对象(iterable)) – spider返回的result
spider (Spider 对象) – 其结果被处理的spider
当spider或(其他spider中间件的) process_spider_input() 跑出异常时, 该方法被调用。
`process_spider_exception()` 必须要么返回 None , 要么返回一个包含 Response 或 Item 对象的可迭代对象(iterable)。
如果其返回 None ,Scrapy将继续处理该异常,调用中间件链中的其他中间件的 process_spider_exception() 方法,直到所有中间件都被调用,该异常到达引擎(异常将被记录并被忽略)。
如果其返回一个可迭代对象,则中间件链的 process_spider_output() 方法被调用, 其他的 process_spider_exception() 将不会被调用。
参数:
response (Response 对象) – 异常被抛出时被处理的response
exception (Exception 对象) – 被跑出的异常
spider (Spider 对象) – 抛出该异常的spider
0.15 新版功能.
该方法以spider 启动的request为参数被调用,执行的过程类似于 process_spider_output() ,只不过其没有相关联的response并且必须返回request(不是item)。
其接受一个可迭代的对象(start_requests 参数)且必须返回另一个包含 Request 对象的可迭代对象。
注解
当在您的spider中间件实现该方法时, 您必须返回一个可迭代对象(类似于参数start_requests)且不要遍历所有的 start_requests。 该迭代器会很大(甚至是无限),进而导致内存溢出。 Scrapy引擎在其具有能力处理start request时将会拉起request, 因此start request迭代器会变得无限,而由其他参数来停止spider( 例如时间限制或者item/page记数)。
参数:
start_requests (包含 Request 的可迭代对象) – start requests
spider (Spider 对象) – start requests所属的spider
BOT_NAME
CONCURRENT_ITEMS
CONCURRENT_REQUESTS
DEFAULT_REQUEST_HEADERS
默认: 如下
{
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
}
Scrapy HTTP Request使用的默认header。
DEPTH_LIMIT
DOWNLOAD_DELAY
DOWNLOAD_DELAY = 0.25 # 250 ms of delay
DOWNLOAD_TIMEOUT
ITEM_PIPELINES
默认: {}
保存项目中启用的pipeline及其顺序的字典。该字典默认为空,值(value)任意,不过值(value)习惯设置在0-1000范围内,值越小优先级越高。
ITEM_PIPELINES = {
'mySpider.pipelines.SomethingPipeline': 300,
'mySpider.pipelines.ItcastJsonPipeline': 800,
}
LOG_ENABLED
LOG_ENCODING
LOG_LEVEL
USER_AGENT
PROXIES:
代理设置
示例:
PROXIES = [
{'ip_port': '111.11.228.75:80', 'password': ''},
{'ip_port': '120.198.243.22:80', 'password': ''},
{'ip_port': '111.8.60.9:8123', 'password': ''},
{'ip_port': '101.71.27.120:80', 'password': ''},
{'ip_port': '122.96.59.104:80', 'password': ''},
{'ip_port': '122.224.249.122:8088', 'password':''},
]
COOKIES_ENABLED = False
process_item(item, spider)
open_spider(spider)
close_spider(spider)
from scrapy.exceptions import DropItem
class PricePipeline(object):
vat_factor = 1.15
def process_item(self, item, spider):
if item['price']:
if item['price_excludes_vat']:
item['price'] = item['price'] * self.vat_factor
return item
else:
raise DropItem("Missing price in %s" % item)
import json
class JsonWriterPipeline(object):
def __init__(self):
self.file = open('items.jl', 'wb')
def process_item(self, item, spider):
line = json.dumps(dict(item)) + "\n"
self.file.write(line)
return item
from scrapy.exceptions import DropItem
class DuplicatesPipeline(object):
def __init__(self):
self.ids_seen = set()
def process_item(self, item, spider):
if item['id'] in self.ids_seen:
raise DropItem("Duplicate item found: %s" % item)
else:
self.ids_seen.add(item['id'])
return item
ITEM_PIPELINES = {
'myproject.pipelines.PricePipeline': 300,
'myproject.pipelines.JsonWriterPipeline': 800,
}
https://edu.csdn.net/courses/o280/p1
(第一页)https://edu.csdn.net/courses/o280/p2
(第二页)scrapy startproject educsdn
educsdn
├── educsdn
│ ├── __init__.py
│ ├── __pycache__
│ ├── items.py # Items的定义,定义抓取的数据结构
│ ├── middlewares.py # 定义Spider和DownLoader的Middlewares中间件实现。
│ ├── pipelines.py # 它定义Item Pipeline的实现,即定义数据管道
│ ├── settings.py # 它定义项目的全局配置
│ └── spiders # 其中包含一个个Spider的实现,每个Spider都有一个文件
│ ├── __init__.py
│ └── __pycache__
└── scrapy.cfg #Scrapy部署时的配置文件,定义了配置文件路径、部署相关信息等内容
scrapy genspider courses edu.csdn.net $ tree ├── demo │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-36.pyc │ │ └── settings.cpython-36.pyc │ ├── items.py │ ├── middlewares.py │ ├── pipelines.py │ ├── settings.py │ └── spiders │ ├── __init__.py │ ├── __pycache__ │ │ └── __init__.cpython-36.pyc │ └── courses.py #在spiders目录下有了一个爬虫类文件courses.py └── scrapy.cfg # courses.py的文件代码如下: # -*- coding: utf-8 -*- import scrapy class CoursesSpider(scrapy.Spider): name = 'courses' allowed_domains = ['edu.csdn.net'] start_urls = ['http://edu.csdn.net/'] def parse(self, response): pass
import scrapy
class CoursesItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
title = scrapy.Field()
url = scrapy.Field()
pic = scrapy.Field()
teacher = scrapy.Field()
time = scrapy.Field()
price = scrapy.Field()
#pass
# -*- coding: utf-8 -*- import scrapy from educsdn.items import CoursesItem class CoursesSpider(scrapy.Spider): name = 'courses' allowed_domains = ['edu.csdn.net'] start_urls = ['https://edu.csdn.net/courses/o280/p1'] p=1 def parse(self, response): #解析并输出课程标题 #print(response.selector.css("div.course_dl_list span.title::text").extract()) #获取所有课程 dlist = response.selector.css("div.course_dl_list") #遍历课程,并解析信息后封装到item容器中 for dd in dlist: item = CoursesItem() item['title'] = dd.css("span.title::text").extract_first() item['url'] = dd.css("a::attr(href)").extract_first() item['pic'] = dd.css("img::attr(src)").extract_first() item['teacher'] = dd.re_first("<p>讲师:(.*?)</p>") item['time'] = dd.re_first("<em>([0-9]+)</em>课时") item['price'] = dd.re_first("¥([0-9\.]+)") #print(item) #print("="*70) yield item #获取前10页的课程信息 self.p += 1 if self.p <= 10: next_url = 'https://edu.csdn.net/courses/o280/p'+str(self.p) url = response.urljoin(next_url) #构建绝对url地址(这里可省略) yield scrapy.Request(url=url,callback=self.parse)
csdndb
和数据表courses
CREATE TABLE `courses` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(255) DEFAULT NULL,
`url` varchar(255) DEFAULT NULL,
`pic` varchar(255) DEFAULT NULL,
`teacher` varchar(32) DEFAULT NULL,
`time` varchar(16) DEFAULT NULL,
`price` varchar(16) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
import pymysql from scrapy.exceptions import DropItem class EducsdnPipeline(object): def process_item(self, item, spider): if item['price'] == None: raise DropItem("Drop item found: %s" % item) else: return item class MysqlPipeline(object): def __init__(self,host,database,user,password,port): self.host = host self.database = database self.user = user self.password = password self.port = port self.db=None self.cursor=None @classmethod def from_crawler(cls,crawler): return cls( host = crawler.settings.get("MYSQL_HOST"), database = crawler.settings.get("MYSQL_DATABASE"), user = crawler.settings.get("MYSQL_USER"), password = crawler.settings.get("MYSQL_PASS"), port = crawler.settings.get("MYSQL_PORT") ) def open_spider(self,spider): self.db = pymysql.connect(self.host,self.user,self.password,self.database,charset='utf8',port=self.port) self.cursor = self.db.cursor() def process_item(self, item, spider): sql = "insert into courses(title,url,pic,teacher,time,price) values('%s','%s','%s','%s','%s','%s')"%(item['title'],item['url'],item['pic'],item['teacher'],str(item['time']),str(item['price'])) #print(item) self.cursor.execute(sql) self.db.commit() return item def close_spider(self,spider): self.db.close()
ITEM_PIPELINES = {
'educsdn.pipelines.EducsdnPipeline': 300,
'educsdn.pipelines.MysqlPipeline': 301,
}
MYSQL_HOST = 'localhost'
MYSQL_DATABASE = 'csdndb'
MYSQL_USER = 'root'
MYSQL_PASS = ''
MYSQL_PORT = 3306
执行如下命令来启用数据爬取
scrapy crawl courses
7.4 下载和处理文件和图像:
ItemPipeline
类继承 scrapy.pipelines.images.ImagesPipeline
。IMAGES_STORE = './images'
images
文件夹 (与scrapy.cfg文件同级)。from scrapy import Request from scrapy.exceptions import DropItem from scrapy.pipelines.images import ImagesPipeline class ImagePipeline(ImagesPipeline): '''自定义图片存储类''' def get_media_requests(self, item, info): '''通过抓取的item对象获取图片信息,并创建Request请求对象添加调度队列,等待调度执行下载''' yield Request(item['pic']) def file_path(self,request,response=None,info=None): '''返回图片下载后保存的名称,没有此方法Scrapy则自动给一个唯一值作为图片名称''' url = request.url file_name = url.split("/")[-1] return file_name def item_completed(self, results, item, info): ''' 下载完成后的处理方法,其中results内容结构如下说明''' image_paths = [x['path'] for ok, x in results if ok] if not image_paths: raise DropItem("Item contains no images") #item['image_paths'] = image_paths return item
在item_completed()方法中,results参数内容结构如下:
print(results)
[(True, {'url': 'https://img-bss.csdn.net/201803191642534078.png',
'path': '201803191642534078.png',
'checksum': 'cc1368dbc122b6762f3e26ccef0dd105'}
)]
在settings.py文件中配置如下(启用下载):
...
ITEM_PIPELINES = {
'educsdn.pipelines.EducsdnPipeline': 300,
'educsdn.pipelines.ImagePipeline': 301,
'educsdn.pipelines.MysqlPipeline': 302,
}
MYSQL_HOST = "localhost"
MYSQL_DATABASE = "csdndb"
MYSQL_USER = "root"
MYSQL_PASS = ""
MYSQL_PORT = 3306
IMAGES_STORE = "./images"
...
社会招聘
信息,搜索条件为北京
地区,Python
关键字的就业岗位,并将信息存储到MySql数据库中。scrapy startproject tencent
tencent
├── tencent
│ ├── __init__.py
│ ├── __pycache__
│ ├── items.py # Items的定义,定义抓取的数据结构
│ ├── middlewares.py # 定义Spider和DownLoader的Middlewares中间件实现。
│ ├── pipelines.py # 它定义Item Pipeline的实现,即定义数据管道
│ ├── settings.py # 它定义项目的全局配置
│ └── spiders # 其中包含一个个Spider的实现,每个Spider都有一个文件
│ ├── __init__.py
│ └── __pycache__
└── scrapy.cfg #Scrapy部署时的配置文件,定义了配置文件路径、部署相关信息等内容
scrapy genspider hr hr.tencent.com $ tree ├── tencent │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-36.pyc │ │ └── settings.cpython-36.pyc │ ├── items.py │ ├── middlewares.py │ ├── pipelines.py │ ├── settings.py │ └── spiders │ ├── __init__.py │ ├── __pycache__ │ │ └── __init__.cpython-36.pyc │ └── hr.py #在spiders目录下有了一个爬虫类文件hr.py └── scrapy.cfg # hr.py的文件代码如下: # -*- coding: utf-8 -*- import scrapy class HrSpider(scrapy.Spider): name = 'hr' allowed_domains = ['hr.tencent.com'] start_urls = ['https://hr.tencent.com/position.php?keywords=python&lid=2156'] def parse(self, response): #解析当前招聘列表信息的url地址: detail_urls = response.css('tr.even a::attr(href),tr.odd a::attr(href)').extract() #遍历url地址 for url in detail_urls: #fullurl = 'http://hr.tencent.com/' + url #构建绝对的url地址,效果同上(域名加相对地址) fullurl = response.urljoin(url) print(fullurl)
import scrapy class TencentItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() pass class HrItem(scrapy.Item): ''' 人事招聘信息封装类 (职位id号,名称、位置、类别、要求、人数、职责和要求) ''' table = "hr" #表名 id = scrapy.Field() title = scrapy.Field() location = scrapy.Field() type = scrapy.Field() number = scrapy.Field() duty = scrapy.Field() requirement = scrapy.Field()
# -*- coding: utf-8 -*- import scrapy from tencent.items import HrItem class HrSpider(scrapy.Spider): name = 'hr' allowed_domains = ['hr.tencent.com'] start_urls = ['https://hr.tencent.com/position.php?keywords=python&lid=2156'] def parse(self, response): #解析当前招聘列表信息的url地址: detail_urls = response.css('tr.even a::attr(href),tr.odd a::attr(href)').extract() #遍历url地址 for url in detail_urls: #fullurl = 'http://hr.tencent.com/' + url #构建绝对的url地址,效果同上(域名加相对地址) fullurl = response.urljoin(url) #print(fullurl) # 构造请求准备爬取招聘详情信息,并指定由parse_page()方法解析回调函数 yield scrapy.Request(url=fullurl,callback=self.parse_page) #获取下一页的url地址 next_url = response.css("#next::attr(href)").extract_first() #判断若不是最后一页 if next_url != "javascript:;": url = response.urljoin(next_url) #构造下一页招聘列表信息的爬取 yield scrapy.Request(url=url,callback=self.parse) # 解析详情页 def parse_page(self,response): #构造招聘信息的Item容器对象 item = HrItem() # 解析id号信息,并封装到Item中 item["id"] = response.selector.re_first('οnclick="applyPosition\(([0-9]+)\);"') #标题 item["title"] = response.css('#sharetitle::text').extract_first() #位置 item["location"] = response.selector.re_first('<span class="lightblue l2">工作地点:</span>(.*?)</td>') #类别 item["type"] = response.selector.re_first('<span class="lightblue">职位类别:</span>(.*?)</td>') #人数 item["number"] = response.selector.re_first('<span class="lightblue">招聘人数:</span>([0-9]+)人</td>') #工作职责 duty = response.xpath('//table//tr[3]//li/text()').extract() item["duty"] = ''.join(duty) #工作要求 requirement = response.xpath('//table//tr[4]//li/text()').extract() item["requirement"] = ''.join(requirement) #print(item) #交给管道文件 yield item
mydb
和数据表hr
CREATE TABLE `hr` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(255) DEFAULT NULL,
`location` varchar(32) DEFAULT NULL,
`type` varchar(32) DEFAULT NULL,
`number` varchar(32) DEFAULT NULL,
`duty` text DEFAULT NULL,
`requirement` text DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
import pymysql class TencentPipeline(object): def process_item(self, item, spider): return item class MysqlPipeline(object): def __init__(self,host,user,password,database,port): self.host = host self.user = user self.password = password self.database = database self.port = port @classmethod def from_crawler(cls,crawler): return cls( host = crawler.settings.get("MYSQL_HOST"), user = crawler.settings.get("MYSQL_USER"), password = crawler.settings.get("MYSQL_PASS"), database = crawler.settings.get("MYSQL_DATABASE"), port = crawler.settings.get("MYSQL_PORT"), ) def open_spider(self, spider): '''负责连接数据库''' self.db = pymysql.connect(self.host,self.user,self.password,self.database,charset="utf8",port=self.port) self.cursor = self.db.cursor() def process_item(self, item, spider): '''执行数据表的写入操作''' #组装sql语句 data = dict(item) keys = ','.join(data.keys()) values=','.join(['%s']*len(data)) sql = "insert into %s(%s) values(%s)"%(item.table,keys,values) #指定参数,并执行sql添加 self.cursor.execute(sql,tuple(data.values())) #事务提交 self.db.commit() return item def close_spider(self, spider): '''关闭连接数据库''' self.db.close()
# 忽略爬虫协议 ROBOTSTXT_OBEY = False # 并发量 CONCURRENT_REQUESTS = 1 #下载延迟 DOWNLOAD_DELAY = 0 ITEM_PIPELINES = { #'educsdn.pipelines.EducsdnPipeline': 300, 'educsdn.pipelines.MysqlPipeline': 301, } MYSQL_HOST = 'localhost' MYSQL_DATABASE = 'mydb' MYSQL_USER = 'root' MYSQL_PASS = '' MYSQL_PORT = 3306
执行如下命令来启用数据爬取
scrapy crawl hr
scrapy crawl spider_name
scrpay crawl spider_name -s LOG_FILE=all.log
LOG_FILE = "mySpider.log"
LOG_LEVEL = "INFO"
CRITICAL - 严重错误(critical)
ERROR - 一般错误(regular errors)
WARNING - 警告信息(warning messages)
INFO - 一般信息(informational messages)
DEBUG - 调试信息(debugging messages)
LOG_ENABLED 默认: True,启用logging
LOG_ENCODING 默认: 'utf-8',logging使用的编码
LOG_FILE 默认: None,在当前目录里创建logging输出文件的文件名
LOG_LEVEL 默认: 'DEBUG',log的最低级别
LOG_STDOUT 默认: False 如果为 True,进程所有的标准输出(及错误)将会被重定向到log中。例如,执行 print "hello" ,其将会在Scrapy log中显示
from scrapy import log
log.msg("This is a warning", level=log.WARNING)
pip install selenium
https://chromedriver.storage.googleapis.com/index.html
chromedriver.exe
放置到Python的Scripts目录下。chromedriver
放置到/usr/local/bin/
目录下from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.wait import WebDriverWait #初始化一个浏览器(如:谷歌,使用Chrome需安装chromedriver) driver = webdriver.Chrome() #driver = webdriver.PhantomJS() #无界面浏览器 try: #请求网页 driver.get("https://www.baidu.com") #查找id值为kw的节点对象(搜索输入框) input = driver.find_element_by_id("kw") #模拟键盘输入字串内容 input.send_keys("python") #模拟键盘点击回车键 input.send_keys(Keys.ENTER) #显式等待,最长10秒 wait = WebDriverWait(driver,10) #等待条件:10秒内必须有个id属性值为content_left的节点加载出来,否则抛异常。 wait.until(EC.presence_of_element_located((By.ID,'content_left'))) # 输出响应信息 print(driver.current_url) print(driver.get_cookies()) print(driver.page_source) finally: #关闭浏览器 #driver.close() pass
from selenium import webdriver
driver = webdriver.Chrome() #谷歌 需:ChromeDriver驱动
driver = webdriver.FireFox() #火狐 需:GeckoDriver驱动
driver = webdriver.Edge()
driver = webdriver.Safari()
driver = webdriver.PhantomJS() #无界面浏览器
from selenium import webdriver
driver = webdriver.Chrome()
#driver = webdriver.PhantomJS()
driver.get("http://www.taobao.com")
print(driver.page_source)
#driver.close()
from selenium import webdriver from selenium.webdriver.common.by import By #创建浏览器对象 driver = webdriver.Chrome() #driver = webdriver.PhantomJS() driver.get("http://www.taobao.com") #下面都是获取id属性值为q的节点对象 input = driver.find_element_by_id("q") print(input) input = driver.find_element_by_css_selector("#q") print(input) input = driver.find_element_by_xpath("//*[@id='q']") print(input) #效果同上 input = driver.find_element(By.ID,"q") print(input) #driver.close()
from selenium import webdriver import time #创建浏览器对象 driver = webdriver.Chrome() #driver = webdriver.PhantomJS() driver.get("http://www.taobao.com") #下面都是获取id属性值为q的节点对象 input = driver.find_element_by_id("q") #模拟键盘输入iphone input.send_keys('iphone') time.sleep(3) #清空输入框 input.clear() #模拟键盘输入iPad input.send_keys('iPad') #获取搜索按钮节点 botton = driver.find_element_by_class_name("btn-search") #触发点击动作 botton.click() #driver.close()
from selenium import webdriver from selenium.webdriver import ActionChains import time #创建浏览器对象 driver = webdriver.Chrome() #加载指定url地址 url = 'http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable' driver.get(url) # 切换Frame窗口 driver.switch_to.frame('iframeResult') #获取两个div节点对象 source = driver.find_element_by_css_selector("#draggable") target = driver.find_element_by_css_selector("#droppable") #创建一个动作链对象 actions = ActionChains(driver) #将一个拖拽操作添加到动作链队列中 actions.drag_and_drop(source,target) time.sleep(3) #执行所有存储的操作(顺序被触发) actions.perform() #driver.close()
from selenium import webdriver
#创建浏览器对象
driver = webdriver.Chrome()
#加载指定url地址
driver.get("https://www.zhihu.com/explore")
#执行javascript程序将页面滚动移至底部
driver.execute_script('window.scrollTo(0,document.body.scrollHeight)')
#执行javascript实现一个弹框操作
driver.execute_script('window.alert("Hello Selenium!")')
#driver.close()
from selenium import webdriver from selenium.webdriver import ActionChains #创建浏览器对象 driver = webdriver.Chrome() #加载请求指定url地址 driver.get("https://www.zhihu.com/explore") #获取id属性值为zh-top-link-logo的节点(logo) logo = driver.find_element_by_id("zh-top-link-logo") print(logo) #输出节点对象 print(logo.get_attribute('class')) #节点的class属性值 #获取id属性值为zu-top-add-question节点(提问按钮) input = driver.find_element_by_id("zu-top-add-question") print(input.text) #获取节点间内容 print(input.id) #获取id属性值 print(input.location) #节点在页面中的相对位置 print(input.tag_name) #节点标签名称 print(input.size) #获取节点的大小 #driver.close()
iframe
,也就是子Frame,他可以将一个页面分成多个子父界面。第⑥的动态链
案例from selenium import webdriver #创建浏览器对象 driver = webdriver.Chrome() #使用隐式等待(固定时间) driver.implicitly_wait(2) #加载请求指定url地址 driver.get("https://www.zhihu.com/explore") #获取节点 input = driver.find_element_by_id("zu-top-add-question") print(input.text) #获取节点间内容 #driver.close() from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.wait import WebDriverWait #创建浏览器对象 driver = webdriver.Chrome() #加载请求指定url地址 driver.get("https://www.zhihu.com/explore") #显式等待,最长10秒 wait = WebDriverWait(driver,10) #等待条件:10秒内必须有个id属性值为zu-top-add-question的节点加载出来,否则抛异常。 input = wait.until(EC.presence_of_element_located((By.ID,'zu-top-add-question'))) print(input.text) #获取节点间内容 #driver.close()
from selenium import webdriver
import time
#创建浏览器对象
driver = webdriver.Chrome()
#加载请求指定url地址
driver.get("https://www.baidu.com")
driver.get("https://www.taobao.com")
driver.get("https://www.jd.com")
time.sleep(2)
driver.back() #后退
time.sleep(2) #前进
driver.forward()
#driver.close()
from selenium import webdriver
from selenium.webdriver import ActionChains
#创建浏览器对象
driver = webdriver.Chrome()
#加载请求指定url地址
driver.get("https://www.zhihu.com/explore")
print(driver.get_cookies())
driver.add_cookie({'name':'namne','domain':'www.zhihu.com','value':'zhangsan'})
print(driver.get_cookies())
driver.delete_all_cookies()
print(driver.get_cookies())
#driver.close()
from selenium import webdriver import time #创建浏览器对象 driver = webdriver.Chrome() #加载请求指定url地址 driver.get("https://www.baidu.com") #使用JavaScript开启一个新的选型卡 driver.execute_script('window.open()') print(driver.window_handles) #切换到第二个选项卡,并打开url地址 driver.switch_to_window(driver.window_handles[1]) driver.get("https://www.taobao.com") time.sleep(2) #切换到第一个选项卡,并打开url地址 driver.switch_to_window(driver.window_handles[0]) driver.get("https://www.jd.com") #driver.close()
from selenium import webdriver from selenium.common.exceptions import TimeoutException,NoSuchElementException #创建浏览器对象 driver = webdriver.Chrome() try: #加载请求指定url地址 driver.get("https://www.baidu.com") except TimeoutException: print('Time Out') try: #加载请求指定url地址 driver.find_element_by_id("demo") except NoSuchElementException: print('No Element') finally: #driver.close() pass
'''通过关键字爬取淘宝网站的信息数据''' from selenium import webdriver from selenium.common.exceptions import TimeoutException from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.wait import WebDriverWait from pyquery import PyQuery as pq from urllib.parse import quote KEYWORD = "ipad" MAX_PAGE = 10 # browser = webdriver.Chrome() # browser = webdriver.PhantomJS() #创建谷歌浏览器对象,启用Chrome的Headless无界面模式 chrome_options = webdriver.ChromeOptions() chrome_options.add_argument('--headless') browser = webdriver.Chrome(chrome_options=chrome_options) #显式等待: wait = WebDriverWait(browser, 10) def index_page(page): '''抓取索引页 :param page: 页码''' print('正在爬取第', page, '页') try: url = 'https://s.taobao.com/search?q=' + quote(KEYWORD) browser.get(url) if page > 1: input = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '#mainsrp-pager div.form > input'))) submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '#mainsrp-pager div.form > span.btn.J_Submit'))) input.clear() input.send_keys(page) submit.click() #等待条件:显示当前页号,显式商品 wait.until(EC.text_to_be_present_in_element((By.CSS_SELECTOR, '#mainsrp-pager li.item.active > span'), str(page))) wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.m-itemlist .items .item'))) get_products() except TimeoutException: index_page(page) def get_products(): '''提取商品数据''' html = browser.page_source doc = pq(html) items = doc('#mainsrp-itemlist .items .item').items() for item in items: product = { 'image': item.find('.pic .img').attr('data-src'), 'price': item.find('.price').text(), 'deal': item.find('.deal-cnt').text(), 'title': item.find('.title').text(), 'shop': item.find('.shop').text(), 'location': item.find('.location').text() } print(product) save_data(product) def save_data(result): '''保存数据''' pass def main(): '''遍历每一页''' for i in range(1, MAX_PAGE + 1): index_page(i) browser.close() # 主程序入口 if __name__ == '__main__': main()
基于分布式文件存储
的数据库。由C++语言编写。旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。介于关系数据库和非关系数据库之间的产品
,是非关系数据库当中功能最丰富,最像关系数据库的。RDBMS | MongoDB |
---|---|
数据库 | 数据库 |
表格 | 集合 |
行 | 文档 |
列 | 字段 |
表联合 | 嵌入文档 |
主键 | 主键 (MongoDB 提供了 key 为 _id ) |
c:\>cd c:\
c:\>mkdir data
c:\>cd data
c:\data>mkdir db
c:\data>cd db
c:\data\db>
C:\Program Files\MongoDB\Server\3.4\bin>mongod --dbpath c:\data\db
C:\Program Files\MongoDB\Server\3.4\bin>mongo
db
show dbs
·
use 数据库名称
默认的数据库为测试,如果你没有创建新的数据库,集合将存放在测试数据库中
db.dropDatabase()
db.createCollection(name, options)
show collections
删除集合:
db.集合名称.drop()
db.集合名称.insert(document)
插入文档时,如果不指定_id参数,MongoDB的会为文档分配一个唯一的的ObjectId
例1:
db.stu.insert({name:'gj',gender:1})
例2:
s1={_id:'20160101',name:'hr'}
s1.gender=0
db.stu.insert(s1)
简单查询
db.集合名称.find()
db.集合名称.update(
<query>,
<update>,
{multi: <boolean>}
)
例3:全文档更新
db.stu.update({name:'hr'},{name:'mnc'})
例4:指定属性更新,通过操作符$集
db.stu.insert({name:'hr',gender:0})
db.stu.update({name:'hr'},{$set:{name:'hys'}})
例5:修改多条匹配到的数据
db.stu.update({},{$set:{gender:0}},{multi:true})
db.集合名称.save(document)
db.stu.save({_id:'20160102','name':'yk',gender:1})
db.stu.save({_id:'20160102','name':'wyk'})
db.集合名称.remove(
<query>,
{
justOne: <boolean>
}
)
例:只删除匹配到的第一条
db.stu.remove({gender:0},{justOne:true})
例:全部删除
db.stu.remove({})
创建集合 db.createCollection('sub',{capped:true,size:10}) 插入第一条数据库查询 db.sub.insert({title:'linux',count:10}) db.sub.find() 插入第二条数据库查询 db.sub.insert({title:'web',count:15}) db.sub.find() 插入第三条数据库查询 db.sub.insert({title:'sql',count:8}) db.sub.find() 插入第四条数据库查询 db.sub.insert({title:'django',count:12}) db.sub.find() 插入第五条数据库查询 db.sub.insert({title:'python',count:14}) db.sub.find()
方法限制():用于读取指定数量的文档
db.集合名称.find().limit(NUMBER)
参数号表示要获取文档的条数
如果没有指定参数则显示集合中的所有文档
例1:查询2条学生信息
db.stu.find().limit(2)
在查询到的返回结果中,只选择必要的字段,而不是选择一个文档的整个字段
如:一个文档有5个字段,需要显示只有3个,投影其中3个字段即可
参数为字段与值,值为1表示显示,值为0不显示
db.集合名称.find({},{字段名称:1,...})
特殊:对于_id列默认是显示的,如果不显示需要明确设置为0
例1
db.stu.find({},{name:1,gender:1})
例2
db.stu.find({},{_id:0,name:1,gender:1})
方法sort(),用于对结果集进行排序
db.集合名称.find().sort({字段:1,...})
参数1为升序排列
参数-1为降序排列
例1:根据性别降序,再根据年龄升序
db.stu.find().sort({gender:-1,age:1})
方法count()用于统计结果集中文档条数
db.集合名称.find({条件}).count()
也可以与为
db.集合名称.count({条件})
例1:统计男生人数
db.stu.find({gender:1}).count()
例2:统计年龄大于20的男生人数
b.stu.count({age:{$gt:20},gender:1})
方法distinct()对数据进行去重
db.集合名称.distinct('去重字段',{条件})
例1:查找年龄大于18的性别(去重)
db.stu.distinct('gender',{age:{$gt:18}})
语法 mongodump -h dbhost -d dbname -o dbdirectory -h:服务器地址,也可以指定端口号 -d:需要备份的数据库名称 -o:备份的数据存放位置,此目录中存放着备份出来的数据 例1 sudo mkdir test1bak sudo mongodump -h 192.168.196.128:27017 -d test1 -o ~/Desktop/test1bak 恢复 语法 mongorestore -h dbhost -d dbname --dir dbdirectory -h:服务器地址 -d:需要恢复的数据库实例 --dir:备份数据所在位置 例2 mongorestore -h 192.168.196.128:27017 -d test2 --dir ~/Desktop/test1bak/test1
安装python包
pip install pymongo
使用:
引入包pymongo
import pymongo
连接,创建客户端
client=pymongo.MongoClient("localhost", 27017)
获得数据库test1
db=client.test1
获得集合stu
stu = db.stu
添加文档
s1={name:'gj',age:18}
s1_id = stu.insert_one(s1).inserted_id
查找一个文档
s2=stu.find_one()
查找多个文档1
for cur in stu.find():
print cur
查找多个文档2
cur=stu.find()
cur.next()
cur.next()
cur.next()
获取文档个数
print stu.count()
scrapy startproject scrapyseleniumtest
cd srapytseleniumtest
scrapy genspider taobao www.baobao.com
ROBOTSTXT_OBEY = False
# 定义信息封装类(图片、价格、购买人数、标题、店铺、发货源)
from scrapy import Item, Field
class ProductItem(Item):
collection = 'products'
image = Field()
price = Field()
deal = Field()
title = Field()
shop = Field()
location = Field()
KEYWORDS = ['iPad']
MAX_PAGE = 100
# -*- coding: utf-8 -*- from scrapy import Request, Spider from urllib.parse import quote from scrapyseleniumtest.items import ProductItem class TaobaoSpider(Spider): name = 'taobao' allowed_domains = ['www.taobao.com'] base_url = 'https://s.taobao.com/search?q=' def start_requests(self): for keyword in self.settings.get('KEYWORDS'): for page in range(1, self.settings.get('MAX_PAGE') + 1): url = self.base_url + quote(keyword) yield Request(url=url, callback=self.parse, meta={'page': page}, dont_filter=True) def parse(self, response): pass
# -*- coding: utf-8 -*- from selenium import webdriver from selenium.common.exceptions import TimeoutException from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from scrapy.http import HtmlResponse from logging import getLogger class SeleniumMiddleware(): def __init__(self, timeout=None, service_args=[]): self.logger = getLogger(__name__) self.timeout = timeout self.browser = webdriver.PhantomJS(service_args=service_args) self.browser.set_window_size(1400, 700) self.browser.set_page_load_timeout(self.timeout) self.wait = WebDriverWait(self.browser, self.timeout) def __del__(self): self.browser.close() def process_request(self, request, spider): """ 用PhantomJS抓取页面 :param request: Request对象 :param spider: Spider对象 :return: HtmlResponse """ self.logger.debug('PhantomJS is Starting') page = request.meta.get('page', 1) try: self.browser.get(request.url) if page > 1: input = self.wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, '#mainsrp-pager div.form > input'))) submit = self.wait.until( EC.element_to_be_clickable((By.CSS_SELECTOR, '#mainsrp-pager div.form > span.btn.J_Submit'))) input.clear() input.send_keys(page) submit.click() self.wait.until( EC.text_to_be_present_in_element((By.CSS_SELECTOR, '#mainsrp-pager li.item.active > span'), str(page))) self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.m-itemlist .items .item'))) return HtmlResponse(url=request.url, body=self.browser.page_source, request=request, encoding='utf-8', status=200) except TimeoutException: return HtmlResponse(url=request.url, status=500, request=request) @classmethod def from_crawler(cls, crawler): return cls(timeout=crawler.settings.get('SELENIUM_TIMEOUT'), service_args=crawler.settings.get('PHANTOMJS_SERVICE_ARGS'))
DOWNLOADER_MIDDLEWARES = {
'scrapyseleniumtest.middlewares.SeleniumMiddleware': 543,
}
# -*- coding: utf-8 -*- from scrapy import Request, Spider from urllib.parse import quote from scrapyseleniumtest.items import ProductItem class TaobaoSpider(Spider): name = 'taobao' allowed_domains = ['www.taobao.com'] base_url = 'https://s.taobao.com/search?q=' def start_requests(self): for keyword in self.settings.get('KEYWORDS'): for page in range(1, self.settings.get('MAX_PAGE') + 1): url = self.base_url + quote(keyword) yield Request(url=url, callback=self.parse, meta={'page': page}, dont_filter=True) def parse(self, response): products = response.xpath( '//div[@id="mainsrp-itemlist"]//div[@class="items"][1]//div[contains(@class, "item")]') for product in products: item = ProductItem() item['price'] = ''.join(product.xpath('.//div[contains(@class, "price")]//text()').extract()).strip() item['title'] = ''.join(product.xpath('.//div[contains(@class, "title")]//text()').extract()).strip() item['shop'] = ''.join(product.xpath('.//div[contains(@class, "shop")]//text()').extract()).strip() item['image'] = ''.join(product.xpath('.//div[@class="pic"]//img[contains(@class, "img")]/@data-src').extract()).strip() item['deal'] = product.xpath('.//div[contains(@class, "deal-cnt")]//text()').extract_first() item['location'] = product.xpath('.//div[contains(@class, "location")]//text()').extract_first() yield item
import pymongo class MongoPipeline(object): def __init__(self, mongo_uri, mongo_db): self.mongo_uri = mongo_uri self.mongo_db = mongo_db @classmethod def from_crawler(cls, crawler): return cls(mongo_uri=crawler.settings.get('MONGO_URI'), mongo_db=crawler.settings.get('MONGO_DB')) def open_spider(self, spider): self.client = pymongo.MongoClient(self.mongo_uri) self.db = self.client[self.mongo_db] def process_item(self, item, spider): self.db[item.collection].insert(dict(item)) return item def close_spider(self, spider): self.client.close()
ITEM_PIPELINES = { 'scrapyseleniumtest.pipelines.MongoPipeline': 300, } KEYWORDS = ['iPad'] MAX_PAGE = 100 SELENIUM_TIMEOUT = 20 PHANTOMJS_SERVICE_ARGS = ['--load-images=false', '--disk-cache=true'] MONGO_URI = 'localhost' MONGO_DB = 'taobao'
from urllib.error import URLError from urllib.request import ProxyHandler, build_opener proxy = '127.0.0.1:8888' #需要认证的代理 #proxy = 'username:password@127.0.0.1:8888' #使用ProxyHandler设置代理 proxy_handler = ProxyHandler({ 'http': 'http://' + proxy, 'https': 'https://' + proxy }) #传入参数创建Opener对象 opener = build_opener(proxy_handler) try: response = opener.open('http://httpbin.org/get') print(response.read().decode('utf-8')) except URLError as e: print(e.reason)
import requests
proxy = '127.0.0.1:8888'
#需要认证的代理
#proxy = 'username:password@127.0.0.1:8888'
proxies = {
'http': 'http://' + proxy,
'https': 'https://' + proxy,
}
try:
response = requests.get('http://httpbin.org/get', proxies=proxies)
print(response.text)
except requests.exceptions.ConnectionError as e:
print('Error', e.args)
from selenium import webdriver
service_args = [
'--proxy=127.0.0.1:9743',
'--proxy-type=http',
#'--proxy-auth=username:password' #带认证代理
]
browser = webdriver.PhantomJS(service_args=service_args)
browser.get('http://httpbin.org/get')
print(browser.page_source)
from selenium import webdriver
proxy = '127.0.0.1:9743'
chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument('--proxy-server=http://' + proxy)
chrome = webdriver.Chrome(chrome_options=chrome_options)
chrome.get('http://httpbin.org/get')
#在Scrapy的Downloader Middleware中间件里
...
def process_request(self, request, spider):
request.meta['proxy'] = 'http://127.0.0.1:9743'
...
import requests,random #定义代理池 proxy_list = [ '182.39.6.245:38634', '115.210.181.31:34301', '123.161.152.38:23201', '222.85.5.187:26675', '123.161.152.31:23127', ] # 随机选择一个代理 proxy = random.choice(proxy_list) proxies = { 'http': 'http://' + proxy, 'https': 'https://' + proxy, } try: response = requests.get('http://httpbin.org/get', proxies=proxies) print(response.text) except requests.exceptions.ConnectionError as e: print('Error', e.args)
import requests
# 从代理服务中获取一个代理IP
proxy = requests.get("http://tvp.daxiangdaili.com/ip/?tid=559775358931681&num=1").text
proxies = {
'http': 'http://' + proxy,
'https': 'https://' + proxy,
}
try:
response = requests.get('http://httpbin.org/get', proxies=proxies)
print(response.text)
except requests.exceptions.ConnectionError as e:
print('Error', e.args)
scrapy genspider hb httpbin.org
```python #编写爬虫文件hb.py import scrapy class HbSpider(scrapy.Spider): name = 'hb' allowed_domains = ['httpbin.org'] start_urls = ['http://httpbin.org/get'] def parse(self, response): print(response.body) #编写中间件文件:middlewares.py class HttpbinProxyMiddleware(object): def process_request(self, request, spider): pro_addr = requests.get('http://127.0.0.1:5000/get').text request.meta['proxy'] = 'http://' + pro_addr #修改配置文件settings.py ROBOTSTXT_OBEY = False #关闭爬虫协议 DOWNLOADER_MIDDLEWARES = { 'httpbin.middlewares.HttpbinProxyMiddleware': 543, } #关闭终端输出,改输出到指定日志文件中 LOG_LEVEL= 'DEBUG' LOG_FILE ='log.txt'
python
的,类别为微信
的所有文章信息,并将信息存储到MongoDB中。wenxin
,让后在此库中创建一个集合wx
,最后开启MongoDB数据库scrapy startproject weixin
cd weixin
scrapy genspider wx weixin.sogou.com
ROBOTSTXT_OBEY = False
# 定义信息封装类(标题、摘要、公众号、时间、URL地址)
import scrapy
class WxItem(scrapy.Item):
# define the fields for your item here like:
collection = ‘wx’
title = scrapy.Field()
content = scrapy.Field()
nickname = scrapy.Field()
date = scrapy.Field()
url = scrapy.Field()
# -*- coding: utf-8 -*- import scrapy from weixin.items import WxItem class WxSpider(scrapy.Spider): name = 'wx' allowed_domains = ['weixin.sogou.com'] start_urls = ['http://weixin.sogou.com/weixin?query=python&type=2&page=1&ie=utf8'] def parse(self, response): #解析出当前页面中的所有文章信息 ullist = response.selector.css("ul.news-list li") #遍历文章信息 for ul in ullist: #解析具体信息并封装到item中 item = WxItem() item['title'] = ul.css("h3 a").re_first("<a.*?>(.*?)</a>") item['content'] = ul.css("p.txt-info::text").extract_first() item['nickname'] = ul.css("a.account::text").extract_first() item['date'] = ul.re_first("document.write\(timeConvert\('([0-9]+)'\)\)") item['url'] = ul.css("h3 a::attr(href)").extract_first() print(item) # 交给pipelines(item管道)处理 yield item #解析出下一頁的url地址 next_url = response.selector.css("#sogou_next::attr(href)").extract_first() #判断是否存在 if next_url: url = response.urljoin(next_url) #构建绝对url地址 yield scrapy.Request(url=url,callback=self.parse) #交给调度去继续爬取下一页信息
import pymongo class MongoPipeline(object): ''' 完成MongoDB数据库对Item信息的存储''' def __init__(self, mongo_uri, mongo_db): '''对象初始化''' self.mongo_uri = mongo_uri self.mongo_db = mongo_db @classmethod def from_crawler(cls, crawler): '''通过依赖注入方式实例化当前类,并返回,参数是从配置文件获取MongoDB信息''' return cls(mongo_uri=crawler.settings.get('MONGO_URI'), mongo_db=crawler.settings.get('MONGO_DB')) def open_spider(self, spider): '''Spider开启自动调用此方法,负责连接MongoDB,并选择数据库''' self.client = pymongo.MongoClient(self.mongo_uri) self.db = self.client[self.mongo_db] def process_item(self, item, spider): '''选择对应集合并写入Item信息''' self.db[item.collection].insert(dict(item)) return item def close_spider(self, spider): '''Spider关闭时自动调用,负责关闭MongoDB的连接''' self.client.close()
ITEM_PIPELINES = {
'scrapyseleniumtest.pipelines.MongoPipeline': 300,
}
MONGO_URI = 'localhost'
MONGO_DB = 'taobao'
scrapy crawl wx
2018-05-30 22:40:10 [scrapy.downloadermiddlewares.redirect] DEBUG: Redirecting (
302) to <GET http://weixin.sogou.com/antispider/?from=%2fweixin%3Fquery%3dpython
%26type%3d2%26page%3d1%26ie%3dutf8> from <GET http://weixin.sogou.com/weixin?que
ry=python&type=2&page=1&ie=utf8>
]
# 在middlewares.py文件中定义一个Downloader中间件
import requests
class HttpbinProxyMiddleware(object):
def process_request(self, request, spider):
pro_addr = requests.get('http://tvp.daxiangdaili.com/ip/?tid=559775358931681&num=1').text
request.meta['proxy'] = 'http://' + pro_addr
# 设置启动上面我们写的这个代理
#在settings.py配置文件中.设置我们自定义的Downloader MiddleWares中间件设置:
DOWNLOADER_MIDDLEWARES = {
'httpbin.middlewares.HttpbinProxyMiddleware': 543,
}
练习:没有登录的用户只能看到10页,登陆后才可看到其他页,那么如何实现爬取更多页信息呢?
安装命令: sudo apt-get -y install redis-server
进入命令行模式:
$ redis-cli
127.0.0.1:6379> set 'name' 'zhangsan'
ok
127.0.0.1:6379> get 'name'
"zhangsan"
启停Redis服务:
sudo /etc/init.d/redis-server start
sudo /etc/init.d/redis-server stop
sudo /etc/init.d/redis-server restart
安装命令:brew install redis
启停服务:
brew services start redis
brew services stop redis
brew services restart redis
配置文件:
/usr/local/etc/redis.conf
pip install redis
set命令:设置一个键和值,键存在则只覆盖,返回ok > set 键 值 例如: >set name zhangsan get命令:获取一个键的值,返回值 > get 键 例如:>get name setnx命令:设置一个不存在的键和值(防止覆盖), > setnx 键 值 若键已存在则返回0表示失败 setex命令:设置一个指定有效期的键和值(单位秒) > setex 键 [有效时间] 值 例如: >setex color 10 red 不写有效时间则表示永久有效,等价于set setrange命令:替换子字符串 (替换长度由子子串长度决定) > setrange 键 位置 子字串 > setrange name 4 aa 将name键对应值的第4个位置开始替换 mset命令:批量设置键和值,成功则返回ok > mset 键1 值1 键2 值2 键3 值3 .... msetnx命令:批量设置不存在的键和值,成功则返回ok > msetnx 键1 值1 键2 值2 键3 值3 .... getset命令:获取原值,并设置新值 getrange命令:获取指定范围的值 >getrange 键 0,4 //获取指定0到4位置上的值 mget命令: 批量获取值 >mget 键1 键2 键3.... incr命令: 指定键的值做加加操作,返回加后的结果。 > 键 例如: >incr kid incrby命令: 设置某个键加上指定值 > incrby 键 m //其中m可以是正整数或负整数 decr命令: 指定键的值做减减操作,返回减后的结果。 > decr 键 例如: >decr kid decrby命令: 设置某个键减上指定值 > decrby 键 m //其中m可以是正整数或负整数 append命令:给指定key的字符串追加value,返回新字符串值的长度 >append 键 追加字串 strlen求长度 >strlen 键名 //返回对应的值。
hset命令:设置一个哈希表的键和值 >hset hash名 键 值 如:>hset user:001 name zhangsan hsetnx命令:设置一个哈希表中不存在的键和值 >hsetnx hash名 键 值 //成功返回1,失败返回0 如:>hsetnx user:001 name zhangsan hmset命令: 批量设置 hget命令: 获取执行哈希名中的键对应值 hexists user:001 name //是否存在, 若存在返回1 hlen user:001 //获取某哈希user001名中键的数量 hdel user:001 name //删除哈希user:001 中name键 hkeys user:002 //返回哈希名为user:002中的所有键。 hvals user:002 //返回哈希名为user:002中的所有值。 hgetall user:002 //返回哈希名为user:002中的所有键和值。
>lpush list1 "world" //在list1头部压入一个字串 >lpush list1 "hello" // 在list1头部压入一个字串 >lrange list1 0 -1 //获取list1中内容 0:表示开头 -1表示结尾。 >rpush list2 "world" //在list2尾部压入一个字串 >rpush list2 "hello" // 在list2尾部压入一个字串 >lrange list2 0 -1 //获取list2中内容 0:表示开头 -1表示结尾。 >linsert list2 before "hello" "there" 在key对应list的特定位置前或后添加字符串 >lset list2 1 "four" 修改指定索引位置上的值 >lrem list2 2 "hello" //删除前两个hello值 >lrem list2 -2 "hello" //删除后两个hello值 >lrem list2 0 "hello" //删除所有hello值 >ltrim mylist8 1 -1 //删除此范围外的值 >lpop list2 //从list2的头部删除元素,并返回删除元素 >rpop list2 //从list2的尾部删除元素,并返回删除元素 >rpoplpush list1 list2 //将list1的尾部一个元素移出到list2头部。并返回 >lindex list2 1 //返回list2中索引位置上的元素 >llen list2 //返回list2上长度
>sadd myset "hello" //向myset中添加一个元素 成功返回1,失败(重复)返回0 >smembers myset //获取myset中的所有元素 >srem myset "one" //从myset中删除一个one 成功返回1,失败(不存在)返回0 >spop myset //随机返回并删除myset中的一个元素 >sdiff myset1 myset2 //返回两个集合的差集 以myset1为标准,获取myset2中不存在的。 > sinter myset2 myset3 交集 > sunion myset2 myset3 并集 > scard myset2 返回元素个数 > sismember myset2 two 判断myset2中是否包含two
向名称为 key 的 zset 中添加元素 member,score 用于排序。如果该元素已经存在,则根据 score 更新该元素的顺序 redis 127.0.0.1:6379> zadd myzset 1 "one" 添加 (integer) 1 redis 127.0.0.1:6379> zadd myzset 2 "two" (integer) 1 redis 127.0.0.1:6379> zadd myzset 3 "two" (integer) 0 redis 127.0.0.1:6379> zrange myzset 0 -1 withscores 查看 1) "one" 2) "1" 3) "two" 4) "3" redis 127.0.0.1:6379> zrem myzset two 删除 (integer) 1 redis 127.0.0.1:6379> zrange myzset 0 -1 withscores 查看 1) "one" 2) "1" redis 127.0.0.1:6379>
1. 键值相关命令
>keys * //返回键(key)
>keys list* //返回名以list开头的所有键(key)
>exists list1 //判断键名为list1的是否存在
存在返回1, 不存在返回0
>del list1 //删除一个键(名为list1)
>expire list1 10 //设置键名为list1的过期时间为10秒后
>ttl list1 //查看键名为list1的过期时间,若为-1表示以过期
>move age 1 //将键名age的转移到1数据库中。
>select 1 //表示进入到1数据库中,默认在0数据库
>persist age //移除age的过期时间(设置为过期)
1. 安全性:为Redis添加密码 ------------------------------- 1.进入配置文件: vi /usr/local/redis/etc/redis.conf 设置:requirepass redis的密码 2. 重启服务: # ./redis-cli shutdown 执行关闭 # ./redis-server /usr/local/redis/etc/redis.conf 启动 3. 登录(两种) # ./redis-cli 客户端命令链接服务器 >auth 密码值 //授权后方可使用 # ./redis-cli -a 密码 //连接时指定密码来进行授权 2. 主从复制 ------------------------------------------ 操作步骤: 1.先将linux虚拟机关闭,之后克隆一个。 2.启动两个虚拟机:master(主)和slave(从) 3. 在slave(从)中配置一下ip地址 # ifconfig eth0 192.168.128.229 # ping 一下看看通不通。 4. 配置从机 进入:配置文件 slaveof 192.168.128.228 6379 //配置连接主机的Redis的ip和端口 masterauth 密码 //配置连接密码 最后启动slave(从)机的Redis服务。 其他:可以通过info命令中的role属性查看自己角色是master、slave 3. 事务处理 -------------------------------------------- >multi //开启一个事务 >set age 10 //暂存指令队列 >set age 20 >exec //开始执行(提交事务) 或>discard //清空指令队列(事务回滚) 4. 乐观锁 ----------------------------------- 在事务前对被操作的属性做一个: > watch age >multi //开启一个事务(在此期间有其他修改,则此处会失败) >set age 10 //暂存指令队列 >set age 20 >exec //开始执行(提交事务) 或>discard //清空指令队列(事务回滚) 5. 持久化机制(通过修改配置文件做设置) ----------------------------------- 1. snapshotting(快照)默认方式 配置 save save 900 1 #900秒内如果超过1个key被修改,则发起快照保存 save 300 10 #300秒内容如超过10个key被修改,则发起快照保存 save 60 10000 2. Append-only file(aof方式) 配置 appendonly on 改为yes 会在bin目录下产生一个.aof的文件 关于aof的配置 appendonly yes //启用aof 持久化方式 # appendfsync always //收到写命令就立即写入磁盘,最慢,但是保证完全的持久化 appendfsync everysec //每秒钟写入磁盘一次,在性能和持久化方面做了很好的折中 # appendfsync no //完全依赖os,性能最好,持久化没保证 6. 发布及订阅消息 ---------------------- 需要开启多个会话端口 会话1:>subscribe tv1 //监听tv1频道 会话2:>subscribe tv1 tv2 //监听tv1和tv2频道 会话3: >publish tv1 消息 //向tv1频道发送一个消息 7. 使用虚拟内存 ------------------------------- 在redis配置文件中设置 vm-enabled yes #开启vm功能 vm-swap-file /tmp/redis.swap #交换出来的value保存的文件路径 vm-max-memory 1000000 #redis使用的最大内存上限 vm-page-size 32 #每个页面的大小32字节 vm-pages 134217728 #最多使用多少页面 vm-max-threads 4 #用于执行value对象换入患处的工作线程数量
import redis # host是redis主机,需要redis服务端和客户端都启动 redis默认端口是6379 r = redis.Redis(host='localhost', port=6379, decode_responses=True) # 字串操作 r.set('name', 'junxi') # key是"foo" value是"bar" 将键值对存入redis缓存 print(r['name']) print(r.get('name')) # 取出键name对应的值 print(type(r.get('name'))) # 如果键fruit不存在,那么输出是True;如果键fruit已经存在,输出是None print(r.set('fruit', 'watermelon', nx=True)) # True--不存在 print(r.setnx('fruit1', 'banana')) # fruit1不存在,输出为True #设置过期时间 r.setex("fruit2", "orange", 5) time.sleep(5) print(r.get('fruit2')) # 5秒后,取值就从orange变成None print(r.mget("fruit", "fruit1", "fruit2", "k1", "k2")) # 将目前redis缓存中的键对应的值批量取出来
r.hset("hash1", "k1", "v1")
r.hset("hash1", "k2", "v2")
print(r.hkeys("hash1")) # 取hash中所有的key
print(r.hget("hash1", "k1")) # 单个取hash的key对应的值
print(r.hmget("hash1", "k1", "k2")) # 多个取hash的key对应的值
r.hsetnx("hash1", "k2", "v3") # 只能新建
print(r.hget("hash1", "k2"))
#hash的批量操作
r.hmset("hash2", {"k2": "v2", "k3": "v3"})
print(r.hget("hash2", "k2")) # 单个取出"hash2"的key-k2对应的value
print(r.hmget("hash2", "k2", "k3")) # 批量取出"hash2"的key-k2 k3对应的value --方式1
print(r.hmget("hash2", ["k2", "k3"])) # 批量取出"hash2"的key-k2 k3对应的value --方式2
print(r.hgetall("hash1")) #取出所有的键值对
r.lpush("list1", 11, 22, 33) print(r.lrange('list1', 0, -1)) r.rpush("list2", 11, 22, 33) # 表示从右向左操作 print(r.llen("list2")) # 列表长度 print(r.lrange("list2", 0, 3)) # 切片取出值,范围是索引号0-3 r.rpush("list2", 44, 55, 66) # 在列表的右边,依次添加44,55,66 print(r.llen("list2")) # 列表长度 print(r.lrange("list2", 0, -1)) # 切片取出值,范围是索引号0到-1(最后一个元素) r.lset("list2", 0, -11) # 把索引号是0的元素修改成-11 print(r.lrange("list2", 0, -1)) r.lrem("list2", "11", 1) # 将列表中左边第一次出现的"11"删除 print(r.lrange("list2", 0, -1)) r.lrem("list2", "99", -1) # 将列表中右边第一次出现的"99"删除 print(r.lrange("list2", 0, -1)) r.lrem("list2", "22", 0) # 将列表中所有的"22"删除 print(r.lrange("list2", 0, -1)) r.lpop("list2") # 删除列表最左边的元素,并且返回删除的元素 print(r.lrange("list2", 0, -1)) r.rpop("list2") # 删除列表最右边的元素,并且返回删除的元素 print(r.lrange("list2", 0, -1)) print(r.lindex("list2", 0)) # 取出索引号是0的值
#新增 r.sadd("set1", 33, 44, 55, 66) # 往集合中添加元素 print(r.scard("set1")) # 集合的长度是4 print(r.smembers("set1")) # 获取集合中所有的成员 print(r.sscan("set1")) #获取集合中所有的成员--元组形式 for i in r.sscan_iter("set1"): print(i) #差集 r.sadd("set2", 11, 22, 33) print(r.smembers("set1")) # 获取集合中所有的成员 print(r.smembers("set2")) print(r.sdiff("set1", "set2")) # 在集合set1但是不在集合set2中 print(r.sdiff("set2", "set1")) # 在集合set2但是不在集合set1中
]
在Scrapy中,爬虫运行时的Request队列放在内存中。爬虫运行中断后,这个队列的空间就会被释放,导致爬取不能继续。
要做到中断后继续爬取,我们可以将队列中的Request保存起来,下次爬取直接读取保存的数据既可继续上一次爬取的队列。
在Scrapy中制定一个爬取队列的存储路径即可,这个路径使用
JOB_DIR
变量来标识,命令如下:
scrapy crawl spider -s JOB_DIR=crawls/spider
更多详细使用请详见官方文档:http://doc.scrapy.org/en/latest/topics/jobs.html
在Scrapy中,我们实际是把爬取队列保存到本地,第二次爬取直接读取并恢复队列既可。
在分布式框架中就不用担心这个问题了,因为爬取队列本身就是用数据库存储的,中断后再启动就会接着上次中断的地方继续爬取。
当Redis的队列为空时,爬虫会重新爬取;当队列不为空时,爬虫便会接着上次中断支处继续爬取。
Scrapy-Redis
分布式爬虫的开源包,直接使用就可以很方便实现分布式爬虫。]
既然这么好能实现分布式爬取,那都需要准备什么呢?
需要准备的东西比较多,都有:
为什么要有mysql呢?是因为我们打算把收集来的数据存放到mysql中
安装:
$ pip install scrapy-redis
$ pip install redis
Scrapy-Redis的官方网址:https://github.com/rmax/scrapy-redis
# 指定使用scrapy-redis的去重 DUPEFILTER_CLASS = 'scrapy_redis.dupefilters.RFPDupeFilter' # 指定使用scrapy-redis的调度器 SCHEDULER = "scrapy_redis.scheduler.Scheduler" # 在redis中保持scrapy-redis用到的各个队列,从而允许暂停和暂停后恢复,也就是不清理redis queues SCHEDULER_PERSIST = True # 指定排序爬取地址时使用的队列, # 默认的 按优先级排序(Scrapy默认),由sorted set实现的一种非FIFO、LIFO方式。 SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.SpiderPriorityQueue' REDIS_URL = None # 一般情况可以省去 REDIS_HOST = '127.0.0.1' # 也可以根据情况改成 localhost REDIS_PORT = 6379
from scrapy.item import Item, Field from scrapy.loader import ItemLoader from scrapy.loader.processors import MapCompose, TakeFirst, Join class ExampleItem(Item): name = Field() description = Field() link = Field() crawled = Field() spider = Field() url = Field() class ExampleLoader(ItemLoader): default_item_class = ExampleItem default_input_processor = MapCompose(lambda s: s.strip()) default_output_processor = TakeFirst() description_out = Join()
from scrapy_redis.spiders import RedisSpider class MySpider(RedisSpider): """Spider that reads urls from redis queue (myspider:start_urls).""" name = 'myspider_redis' redis_key = 'myspider:start_urls' def __init__(self, *args, **kwargs): # Dynamically define the allowed domains list. domain = kwargs.pop('domain', '') self.allowed_domains = filter(None, domain.split(',')) super(MySpider, self).__init__(*args, **kwargs) def parse(self, response): return { 'name': response.css('title::text').extract_first(), 'url': response.url, }
$ scrapy runspider my.py
可以输入多个来观察多进程的效果。。打开了爬虫之后你会发现爬虫处于等待爬取的状态,是因为list此时为空。所以需要在redis控制台中添加启动地址,这样就可以愉快的看到所有的爬虫都动起来啦。
lpush mycrawler:start_urls http://www.***.com
# 指定使用scrapy-redis的调度器 SCHEDULER = "scrapy_redis.scheduler.Scheduler" # 指定使用scrapy-redis的去重 DUPEFILTER_CLASS = 'scrapy_redis.dupefilters.RFPDupeFilter' # 指定排序爬取地址时使用的队列, # 默认的 按优先级排序(Scrapy默认),由sorted set实现的一种非FIFO、LIFO方式。 SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.SpiderPriorityQueue' # 可选的 按先进先出排序(FIFO) # SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.SpiderQueue' # 可选的 按后进先出排序(LIFO) # SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.SpiderStack' # 在redis中保持scrapy-redis用到的各个队列,从而允许暂停和暂停后恢复,也就是不清理redis queues SCHEDULER_PERSIST = True # 只在使用SpiderQueue或者SpiderStack是有效的参数,指定爬虫关闭的最大间隔时间 # SCHEDULER_IDLE_BEFORE_CLOSE = 10 # 通过配置RedisPipeline将item写入key为 spider.name : items 的redis的list中,供后面的分布式处理item # 这个已经由 scrapy-redis 实现,不需要我们写代码 ITEM_PIPELINES = { 'example.pipelines.ExamplePipeline': 300, 'scrapy_redis.pipelines.RedisPipeline': 400 } # 指定redis数据库的连接参数 # REDIS_PASS是我自己加上的redis连接密码(默认不做) REDIS_HOST = '127.0.0.1' REDIS_PORT = 6379 #REDIS_PASS = 'redisP@ssw0rd' # LOG等级 LOG_LEVEL = 'DEBUG' #默认情况下,RFPDupeFilter只记录第一个重复请求。将DUPEFILTER_DEBUG设置为True会记录所有重复的请求。 DUPEFILTER_DEBUG =True # 覆盖默认请求头,可以自己编写Downloader Middlewares设置代理和UserAgent DEFAULT_REQUEST_HEADERS = { 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 'Accept-Language': 'zh-CN,zh;q=0.8', 'Connection': 'keep-alive', 'Accept-Encoding': 'gzip, deflate, sdch' }
Scrapy框架的使用
中的案例demo复制过来两份:master(主)、slave(从)]
import scrapy
class FangItem(scrapy.Item):
# define the fields for your item here like:
title = scrapy.Field()
address = scrapy.Field()
time = scrapy.Field()
clicks = scrapy.Field()
price = scrapy.Field()
# -*- coding: utf-8 -*- import scrapy from demo.items import FangItem from scrapy_redis.spiders import RedisSpider class FangSpider(RedisSpider): name = 'fang' #allowed_domains = ['fang.5i5j.com'] #start_urls = ['https://fang.5i5j.com/bj/loupan/'] redis_key = 'fangspider:start_urls' def __init__(self, *args, **kwargs): # Dynamically define the allowed domains list. domain = kwargs.pop('domain', '') self.allowed_domains = filter(None, domain.split(',')) super(FangSpider, self).__init__(*args, **kwargs) def parse(self, response): #print(response.status) hlist = response.css("div.houseList_list") for vo in hlist: item = FangItem() item['title'] = vo.css("h3.fontS20 a::text").extract_first() item['address'] = vo.css("span.addressName::text").extract_first() item['time'] = vo.re("<span>(.*?)开盘</span>")[0] item['clicks'] = vo.re("<span><i>([0-9]+)</i>浏览</span>")[0] item['price'] = vo.css("i.fontS24::text").extract_first() print(item) yield item #pass
class DemoPipeline(object):
def process_item(self, item, spider):
print("="*70)
return item
... ITEM_PIPELINES = { #'demo.pipelines.DemoPipeline': 300, 'scrapy_redis.pipelines.RedisPipeline': 400, } ... # 指定使用scrapy-redis的去重 DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter' # 指定使用scrapy-redis的调度器 SCHEDULER = "scrapy_redis.scheduler.Scheduler" # 在redis中保持scrapy-redis用到的各个队列,从而允许暂停和暂停后恢复,也就是不清理redis queues SCHEDULER_PERSIST = True # 指定排序爬取地址时使用的队列, # 默认的 按优先级排序(Scrapy默认),由sorted set实现的一种非FIFO、LIFO方式。 SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.SpiderPriorityQueue' # REDIS_URL = 'redis://localhost:6379' # 一般情况可以省去 REDIS_HOST = 'localhost' # 也可以根据情况改成 localhost REDIS_PORT = 6379
# 进入爬虫文件目录找到爬虫文件:
$ scrapy runspider fang.py
另启一个终端,并连接redis数据库
$ redis_cli -p 6379
6379 >lpush fangspider:start_urls https://fang.5i5j.com/bj/loupan/
import scrapy
class MasterItem(scrapy.Item):
# define the fields for your item here like:
url = scrapy.Field()
#pass
# -*- coding: utf-8 -*- from scrapy.spider import CrawlSpider,Rule from scrapy.linkextractors import LinkExtractor from demo.items import MasterItem class FangSpider(CrawlSpider): name = 'master' allowed_domains = ['fang.5i5j.com'] start_urls = ['https://fang.5i5j.com/bj/loupan/'] item = MasterItem() #Rule是在定义抽取链接的规则 rules = ( Rule(LinkExtractor(allow=('https://fang.5i5j.com/bj/loupan/n[0-9]+/',)), callback='parse_item', follow=True), ) def parse_item(self,response): item = self.item item['url'] = response.url return item
import redis,re class MasterPipeline(object): def __init__(self,host,port): #连接redis数据库 self.r = redis.Redis(host=host, port=port, decode_responses=True) #self.redis_url = 'redis://password:@localhost:6379/' #self.r = redis.Redis.from_url(self.redis_url,decode_responses=True) @classmethod def from_crawler(cls,crawler): '''注入实例化对象(传入参数)''' return cls( host = crawler.settings.get("REDIS_HOST"), port = crawler.settings.get("REDIS_PORT"), ) def process_item(self, item, spider): #使用正则判断url地址是否有效,并写入redis。 if re.search('/bj/loupan/',item['url']): self.r.lpush('fangspider:start_urls', item['url']) else: self.r.lpush('fangspider:no_urls', item['url'])
ITEM_PIPELINES = { 'demo.pipelines.MasterPipeline': 300, #'scrapy_redis.pipelines.RedisPipeline': 400, } ... # 指定使用scrapy-redis的去重 DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter' # 指定使用scrapy-redis的调度器 SCHEDULER = "scrapy_redis.scheduler.Scheduler" # 在redis中保持scrapy-redis用到的各个队列,从而允许暂停和暂停后恢复,也就是不清理redis queues SCHEDULER_PERSIST = True # 指定排序爬取地址时使用的队列, # 默认的 按优先级排序(Scrapy默认),由sorted set实现的一种非FIFO、LIFO方式。 SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.SpiderPriorityQueue' # REDIS_URL = 'redis:password//127.0.0.1:6379' # 一般情况可以省去 REDIS_HOST = '127.0.0.1' # 也可以根据情况改成 localhost REDIS_PORT = 6379
# 进入爬虫文件目录找到爬虫文件:
$ scrapy runspider fang.py
# process_demo_mongodb.py import json import redis import pymongo def main(): # 指定Redis数据库信息 rediscli = redis.StrictRedis(host='127.0.0.1', port=6379, db=0) # 指定MongoDB数据库信息 mongocli = pymongo.MongoClient(host='localhost', port=27017) # 创建数据库名 db = mongocli['demodb'] # 创建空间 sheet = db['fang'] while True: # FIFO模式为 blpop,LIFO模式为 brpop,获取键值 source, data = rediscli.blpop(["demo:items"]) item = json.loads(data) sheet.insert(item) try: print u"Processing: %(name)s <%(link)s>" % item except KeyError: print u"Error procesing: %r" % item if __name__ == '__main__': main()
]
* 支持SSL代理。可以截取分析SSL的请求。
* 支持流量控制。可以模拟慢速网络以及等待时间(latency)较长的请求。
* 支持AJAX调试。可以自动将json或xml数据格式化,方便查看。
* 支持AMF调试。可以将Flash Remoting 或 Flex Remoting信息格式化,方便查看。
* 支持重发网络请求,方便后端调试。
* 支持修改网络请求参数。
* 支持网络请求的截获并动态修改。
* 检查HTML,CSS和RSS内容是否符合W3C标准。
]
实现手机和电脑在同一局域网下的机上,完成Charles的代理设置:
首先查看电脑的打开Charles代理是否开启,具体操作是:Proxy
-> Proxy Settings
,打开代理设置界面,设置代理端口为:8888. ]
打开手机的网络配置,并设置使用代理配置:
]
安装完成后,我们还需要配置相关SSL证书 来抓取HTTPS协议的信息包。
Windows系统:
Help
->SSL Proxying
->Install Charles Root Certificate
,即可进入证书安装界面。Mac系统:
Help
->SSL Proxying
->Install Charles Root Certificate
,即可进入证书安装界面。IOS手机
:
在网络配置和代理开启的情况下,若是你的手机是IOS系统,可以按照下面的操作进行证书配置。
在手机浏览器上打开chls.pro/ssl后,便会打开证书安装页面,点击安装即可。
]
在IOS手机上,点击“设置”->“通用”->“关于本机”->“证书信任设置”,设置开启即可。
]
在 Charles 设置 SSL 代理:
Proxy –> SSL Proxying Setting –> Enable SSL Proxying
]
]
]
pip3 install mitmproxy
配置手机和PC处于同一局域网下:(具体步骤详见上一节内容)
打开手机的网络配置,并设置使用代理配置,端口监听 8080
:(具体步骤详见上一节内容)
配置mitmproxy的CA证书。
对于mitmproxy来说,如果想要截获HTTPS请求,就需要设置CA证书,而mitmproxy安装后就会提供一套CA证书,只要客户信任了此证书即可。
首先运行启动mitmdump
,就会在此命令下产生CA证书,我们可以从用户目录下的.mitmproxy
目录下看到。
localhost:app zhangtao$ mitmdump
Proxy server listening at http://*:8080
]
文件说明:
mitmproxy-ca.pem
PEM格式的证书私钥mitmproxy-ca-cert.pem
PEM格式证书,适用于大多数非Windows平台mitmproxy-ca-cert.p12
PKCS12格式的证书,适用于大多数Windows平台mitmproxy-ca-cert.cer
与mitmproxy-ca-cert.pem相同(只是后缀名不同),适用于大部分Android平台mitmproxy-dhparam.pem
PEM格式的秘钥文件,用于增强SSL安全性。在Mac系统下双击mitmproxy-ca-cert.pem
即可弹出秘钥串管理页面,找到mitmproxy
证书,打开设置选项,选择始终信任
即可。
]
将mitmproxy-ca-cert.pem
文件发送到iPhone手机上,点击安装就可以了(在IOS上通过AirDrop共享过去的)。
]
在iphone上安装CA证书(Android手机直接复制文件点击安装即可)
]
在IOS手机上,点击“设置” - > “通用” - > “关于本机” - > “证书信任设置”,设置开启即可
]
mitmproxy
命令就会打开一个监听窗口,此窗口就会输出一个App请求中的信息。 localhost:app zhangtao$ mitmproxy
]
按键 | 说明 |
---|---|
q | 退出(相当于返回键,可一级一级返回) |
d | 删除当前(黄色箭头)指向的链接 |
D | 恢复刚才删除的请求 |
G | 跳到最新一个请求 |
g | 跳到第一个请求 |
C | 清空控制台(C是大写) |
i | 可输入需要拦截的文件或者域名(逗号需要用\来做转译,栗子:feezu.cn) |
a | 放行请求 |
A | 放行所有请求 |
? | 查看界面帮助信息 |
^ v | 上下箭头移动光标 |
enter | 查看光标所在列的内容 |
tab | 分别查看 Request 和 Response 的详细信息 |
/ | 搜索body里的内容 |
esc | 退出编辑 |
e | 进入编辑模式 |
mitmdump -w outfile
mitmdump -s script.py
from mitmproxy import ctx
def request(flow):
# 修改请求头
flow.request.headers['User-Agent'] = 'MitmProxy'
ctx.log.info(str(flow.request.headers))
ctx.log.warn(str(flow.request.headers))
ctx.log.error(str(flow.request.headers))
from mitmproxy import ctx
# 所有的请求都会经过request
def request(flow):
info = ctx.log.info
# info(flow.request.url)
# info(str(flow.request.headers))
# info(str(flow.request.cookies))
# info(flow.request.host)
# info(flow.request.method)
# info(str(flow.request.port))
# info(flow.request.scheme)
print(flow.request.method,":",flow.request.url)
from mitmproxy import ctx
# 所有的请求都会经过request
def response(flow):
info = ctx.log.info
# info(flow.response.url)
# info(str(flow.response.headers))
# info(str(flow.response.cookies))
info(str(flow.response.status_code))
# info(str(flow.response.text))
http://httpbin.org/get
测试:我们的抓取目标是京东商城的App电子商品信息,并将信息保存到MongoDB数据库中。
我们将商品信息的id号、标题、单价、评价条数等信息
]
准备工作:
抓取分析:
打开iCharles抓包工具,让后使用手机打开京东App应用程序,让后搜索电脑
商品信息。
在抓包工具中获取url地址:http://api.m.jd.com/client.action?functionId=search
抓取信息格式为json格式。具体如下图所示
]
import json #import pymongo from mitmproxy import ctx #连接MongoDB数据库jddb,选择集合shop #client = pymongo.MongoClient('localhost') #db = client['jddb'] #collection = db['shop'] def response(flow): #global collection url = 'http://api.m.jd.com/client.action?functionId=search' if flow.request.url.startswith(url): text = flow.response.text data = json.loads(text) shops = data.get('wareInfo') for shop in shops: item = { 'spuId': shop.get('spuId'), 'wname': shop.get('wname'), 'price': shop.get('jdPrice'), 'reviews': shop.get('reviews') } ctx.log.info(str(item)) #写入MongoDB数据库 #collection.insert(data)
mitmdump -s script.py
]
]
]
]
3181
个城市信息。URL地址:https://cdn.heweather.com/china-city-list.txt
]
# 从网上读取城市列表信息,并使用正则将数据解析出来。 import requests import re # 爬取城市信息列表 url = "https://cdn.heweather.com/china-city-list.txt" res = requests.get(url) data = res.content.decode('utf-8') # 使用换行符拆分出每一条城市信息数据 dlist = re.split('[\n\r]+',data) # 剔除前两条无用的数据 for i in range(2): dlist.remove(dlist[0]) # 输出城市信息条数 print(len(dlist)) # 输出前20条信息 for i in range(20): #使用空白符拆分出每个字段信息 item = re.split("\s+",dlist[i]) #输出 #print(item) print(item[0],":",item[2])
]
import requests import time #爬取指定城市的天气信息 url = "https://free-api.heweather.com/s6/weather?location=北京&key=a46fd5c4f1b54fda9ee71ba6711f09cd" res = requests.get(url) time.sleep(2) #解析json数据 dlist = res.json() data = dlist['HeWeather6'][0] #输出部分天气信息 print("城市:",data['basic']['location']) print("今日:",str(data['daily_forecast'][0]['date'])) print("温度:",data['daily_forecast'][0]['tmp_min'],"~",data['daily_forecast'][0]['tmp_max']) print(data['daily_forecast'][0]['cond_txt_d']," ~ ",data['daily_forecast'][0]['cond_txt_n']) print(data['daily_forecast'][0]['wind_dir'],data['daily_forecast'][0]['wind_sc'],'级')
城市: 北京
今日: 2018-06-13
温度: 18 ~ 28
雷阵雨 ~ 多云
东北风 1-2 级
# 从网上读取城市列表信息,并遍历部分城市信息,从API接口中爬取天气信息。 import requests import re import time # 爬取城市信息列表 url = "https://cdn.heweather.com/china-city-list.txt" res = requests.get(url) data = res.content.decode('utf-8') # 使用换行符拆分出每一条城市信息数据 dlist = re.split('[\n\r]+',data) # 剔除前两条无用的数据 for i in range(2): dlist.remove(dlist[0]) # 输出城市信息条数 print(len(dlist)) # 输出前10条信息 for i in range(10): #使用空白符拆分出每个字段信息 item = re.split("\s+",dlist[i]) #输出 #print(item) #print(item[0],":",item[2]) #爬取指定城市的天气信息 url = "https://free-api.heweather.com/s6/weather?location=%s&key=a46fd5c4f1b54fda9ee71ba6711f09cd"%(item[0]) res = requests.get(url) time.sleep(2) #解析json数据 datalist = res.json() data = datalist['HeWeather6'][0] #输出部分天气信息 print("城市:",data['basic']['location']) print("今日:",str(data['daily_forecast'][0]['date'])) print("温度:",data['daily_forecast'][0]['tmp_min'],"~",data['daily_forecast'][0]['tmp_max']) print(data['daily_forecast'][0]['cond_txt_d']," . ",data['daily_forecast'][0]['cond_txt_n']) print(data['daily_forecast'][0]['wind_dir'],data['daily_forecast'][0]['wind_sc'],'级') print("="*70)
3181
城市: 北京
今日: 2018-06-13
温度: 18 ~ 28
雷阵雨 . 多云
东北风 1-2 级
======================================================================
城市: 海淀
今日: 2018-06-14
温度: 18 ~ 30
多云 . 多云
南风 1-2 级
======================================================================
城市: 朝阳
... ...
]
https://account.geetest.com/login
,打开极验的管理后台登录页面,完成自动化登录操作。EMAIL = '登录账户' PASSWORD = '登录密码' class CrackGeetest(): def __init__(self): self.url = 'https://account.geetest.com/login' self.browser = webdriver.Chrome() #设置显示等待时间 self.wait = WebDriverWait(self.browser, 20) self.email = EMAIL self.password = PASSWORD def crack(): pass # 程序主入口 if __name__ == '__main__': crack = CrackGeetest() crack.crack()
class CrackGeetest(): #... def get_geetest_button(self): ''' 获取初始验证按钮,return:按钮对象 ''' button = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_radar_tip'))) return button def open(self): ''' 打开网页输入用户名密码, return: None ''' self.browser.get(self.url) email = self.wait.until(EC.presence_of_element_located((By.ID, 'email'))) password = self.wait.until(EC.presence_of_element_located((By.ID, 'password'))) email.send_keys(self.email) password.send_keys(self.password) def crack(self): # 输入用户名密码 self.open() # 点击验证按钮 button = self.get_geetest_button() button.click() #... #...
def get_position(self): ''' 获取验证码位置, return: 验证码位置(元组) ''' img = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_canvas_img'))) time.sleep(2) location = img.location size = img.size top,bottom,left,right = location['y'],location['y']+size['height'],location['x'],location['x']+size['width'] return (top, bottom, left, right) def get_screenshot(self): ''' 获取网页截图, return: 截图对象 ''' #浏览器截屏 screenshot = self.browser.get_screenshot_as_png() screenshot = Image.open(BytesIO(screenshot)) return screenshot def get_geetest_image(self, name='captcha.png'): ''' 获取验证码图片, return: 图片对象 ''' top, bottom, left, right = self.get_position() print('验证码位置', top, bottom, left, right) screenshot = self.get_screenshot() #从网页截屏图片中裁剪处理验证图片 captcha = screenshot.crop((left, top, right, bottom)) captcha.save(name) return captcha def get_slider(self): ''' 获取滑块, return: 滑块对象 ''' slider = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_slider_button'))) return slider def crack(self): #... # 获取验证码图片 image1 = self.get_geetest_image('captcha1.png') # 点按呼出缺口 slider = self.get_slider() slider.click() # 获取带缺口的验证码图片 image2 = self.get_geetest_image('captcha2.png') #...
BORDER = 6 INIT_LEFT = 60 class CrackGeetest(): def get_gap(self, image1, image2): ''' 获取缺口偏移量, 参数:image1不带缺口图片、image2带缺口图片。返回偏移量 ''' left = 65 for i in range(left, image1.size[0]): for j in range(image1.size[1]): if not self.is_pixel_equal(image1, image2, i, j): left = i return left return left def is_pixel_equal(self, image1, image2, x, y): ''' 判断两个像素是否相同 :param image1: 图片1 :param image2: 图片2 :param x: 位置x :param y: 位置y :return: 像素是否相同 ''' # 取两个图片的像素点(R、G、B) pixel1 = image1.load()[x, y] pixel2 = image2.load()[x, y] threshold = 60 if abs(pixel1[0]-pixel2[0])<threshold and abs(pixel1[1]-pixel2[1])<threshold and abs(pixel1[2]-pixel2[2])<threshold: return True else: return False def crack(self): #... # 获取缺口位置 gap = self.get_gap(image1, image2) print('缺口位置', gap) # 减去缺口位移 gap -= BORDER
def get_track(self, distance): ''' 根据偏移量获取移动轨迹 :param distance: 偏移量 :return: 移动轨迹 ''' # 移动轨迹 track = [] # 当前位移 current = 0 # 减速阈值 mid = distance * 4 / 5 # 计算间隔 t = 0.2 # 初速度 v = 0 while current < distance: if current < mid: # 加速度为正2 a = 2 else: # 加速度为负3 a = -3 # 初速度v0 v0 = v # 当前速度v = v0 + at v = v0 + a * t # 移动距离x = v0t + 1/2 * a * t^2 move = v0 * t + 1 / 2 * a * t * t # 当前位移 current += move # 加入轨迹 track.append(round(move)) return track def crack(self): #... # 获取移动轨迹 track = self.get_track(gap) print('滑动轨迹', track)
def move_to_gap(self, slider, track): ''' 拖动滑块到缺口处 :param slider: 滑块 :param track: 轨迹 :return: ''' ActionChains(self.browser).click_and_hold(slider).perform() for x in track: ActionChains(self.browser).move_by_offset(xoffset=x, yoffset=0).perform() time.sleep(0.5) ActionChains(self.browser).release().perform() def crack(self): #... # 拖动滑块 self.move_to_gap(slider, track) success = self.wait.until( EC.text_to_be_present_in_element((By.CLASS_NAME, 'geetest_success_radar_tip_content'), '验证成功')) print(success)
def login(self):
''' 执行登录 return: None '''
submit = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'login-btn')))
submit.click()
time.sleep(10)
print('登录成功')
def crack(self):
#...
# 失败后重试
if not success:
self.crack()
else:
self.login()
import time from io import BytesIO from PIL import Image from selenium import webdriver from selenium.webdriver import ActionChains from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC EMAIL = '122794105@qq.com' PASSWORD = 'python123' BORDER = 6 class CrackGeetest(): def __init__(self): self.url = 'https://account.geetest.com/login' self.browser = webdriver.Chrome() #设置显示等待时间 self.wait = WebDriverWait(self.browser, 20) self.email = EMAIL self.password = PASSWORD def __del__(self): #self.browser.close() pass def get_geetest_button(self): ''' 获取初始验证按钮,return:按钮对象 ''' button = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_radar_tip'))) return button def get_position(self): ''' 获取验证码位置, return: 验证码位置(元组) ''' img = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_canvas_img'))) time.sleep(2) location = img.location size = img.size top,bottom,left,right = location['y'],location['y']+size['height'],location['x'],location['x']+size['width'] return (top, bottom, left, right) def get_screenshot(self): ''' 获取网页截图, return: 截图对象 ''' #浏览器截屏 screenshot = self.browser.get_screenshot_as_png() screenshot = Image.open(BytesIO(screenshot)) return screenshot def get_slider(self): ''' 获取滑块, return: 滑块对象 ''' slider = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_slider_button'))) return slider def get_geetest_image(self, name='captcha.png'): ''' 获取验证码图片, return: 图片对象 ''' top, bottom, left, right = self.get_position() print('验证码位置', top, bottom, left, right) screenshot = self.get_screenshot() #从网页截屏图片中裁剪处理验证图片 captcha = screenshot.crop((left, top, right, bottom)) captcha.save(name) return captcha def open(self): ''' 打开网页输入用户名密码, return: None ''' self.browser.get(self.url) email = self.wait.until(EC.presence_of_element_located((By.ID, 'email'))) password = self.wait.until(EC.presence_of_element_located((By.ID, 'password'))) email.send_keys(self.email) password.send_keys(self.password) def get_gap(self, image1, image2): ''' 获取缺口偏移量, 参数:image1不带缺口图片、image2带缺口图片。返回偏移量 ''' left = 65 for i in range(left, image1.size[0]): for j in range(image1.size[1]): if not self.is_pixel_equal(image1, image2, i, j): left = i return left return left def is_pixel_equal(self, image1, image2, x, y): ''' 判断两个像素是否相同 :param image1: 图片1 :param image2: 图片2 :param x: 位置x :param y: 位置y :return: 像素是否相同 ''' # 取两个图片的像素点(R、G、B) pixel1 = image1.load()[x, y] pixel2 = image2.load()[x, y] threshold = 60 if abs(pixel1[0]-pixel2[0])<threshold and abs(pixel1[1]-pixel2[1])<threshold and abs(pixel1[2]-pixel2[2])<threshold: return True else: return False def get_track(self, distance): ''' 根据偏移量获取移动轨迹 :param distance: 偏移量 :return: 移动轨迹 ''' # 移动轨迹 track = [] # 当前位移 current = 0 # 减速阈值 mid = distance * 4 / 5 # 计算间隔 t = 0.2 # 初速度 v = 0 while current < distance: if current < mid: # 加速度为正2 a = 2 else: # 加速度为负3 a = -3 # 初速度v0 v0 = v # 当前速度v = v0 + at v = v0 + a * t # 移动距离x = v0t + 1/2 * a * t^2 move = v0 * t + 1 / 2 * a * t * t # 当前位移 current += move # 加入轨迹 track.append(round(move)) return track def move_to_gap(self, slider, track): ''' 拖动滑块到缺口处 :param slider: 滑块 :param track: 轨迹 :return: ''' ActionChains(self.browser).click_and_hold(slider).perform() for x in track: ActionChains(self.browser).move_by_offset(xoffset=x, yoffset=0).perform() time.sleep(0.5) ActionChains(self.browser).release().perform() def login(self): ''' 执行登录 return: None ''' submit = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'login-btn'))) submit.click() time.sleep(10) print('登录成功') def crack(self): # 输入用户名密码 self.open() # 点击验证按钮 button = self.get_geetest_button() button.click() # 获取验证码图片 image1 = self.get_geetest_image('captcha1.png') # 点按呼出缺口 slider = self.get_slider() slider.click() # 获取带缺口的验证码图片 image2 = self.get_geetest_image('captcha2.png') # 获取缺口位置 gap = self.get_gap(image1, image2) print('缺口位置', gap) # 减去缺口位移 gap -= BORDER # 获取移动轨迹 track = self.get_track(gap) print('滑动轨迹', track) # 拖动滑块 self.move_to_gap(slider, track) success = self.wait.until( EC.text_to_be_present_in_element((By.CLASS_NAME, 'geetest_success_radar_tip_content'), '验证成功')) print(success) # 失败后重试 if not success: self.crack() else: self.login() # 程序主入口 if __name__ == '__main__': crack = CrackGeetest() crack.crack()
使用Python编程语言编写一个网络爬虫项目,将豆瓣读书网站上的所有图书信息爬取下来,并存储到MySQL数据库中。
爬取信息字段要求:
[ID号、书名、作者、出版社、原作名、译者、出版年、页数、定价、装帧、丛书、ISBN、评分、评论人数]
]
]
所有热门标签
,打开豆瓣图书标签
页面:]
豆瓣图书标签
页面中所有的标签,进行对应标签下图书信息的列表页展示。] ]
]
检测到有异常请求从你的 IP 发出,请`登录`使用豆瓣。
最近需要爬取豆瓣的用户评分数据构造一个数据集,但是在爬取时却出了问题:
豆瓣封IP,白天一分钟可以访问40次,晚上一分钟可以访问60次,超过限制次数就会封IP。
于是,我便去代理IP网站上找了几个代理IP,但是爬取时又碰到了问题,明明已经使用代理IP,但是一旦超过限制次数爬虫仍然不能正常访问豆瓣。
问题出在Cookie上
豆瓣利用封IP+封Cookie来限制爬虫,因此只用代理IP的话也不行,Cookie也要更换。
想法一:
每次使用代理IP时,先访问豆瓣官网获取Cookie再访问用户的评论页面。本以为换了IP,Cookie随之也会更换,其实Cookie并没有改变。
想法二:
伪造Cookie。
观察豆瓣设置的Cookie格式,并进行伪造。
doubandb
books
[
ID号、书名、作者、出版社、原作名、译者、出版年、页数、
定价、装帧、丛书、ISBN、评分、评论人数
]
CREATE TABLE `books` ( `id` bigint(20) unsigned NOT NULL COMMENT 'ID号', `title` varchar(255) DEFAULT NULL COMMENT '书名', `author` varchar(64) DEFAULT NULL COMMENT '作者', `press` varchar(255) DEFAULT NULL COMMENT '出版社', `original` varchar(255) DEFAULT NULL COMMENT '原作名', `translator` varchar(128) DEFAULT NULL COMMENT '译者', `imprint` varchar(128) DEFAULT NULL COMMENT '出版年', `pages` int(10) unsigned DEFAULT NULL COMMENT '页数', `price` double(6,2) unsigned DEFAULT NULL COMMENT '定价', `binding` varchar(32) DEFAULT NULL COMMENT '装帧', `series` varchar(128) DEFAULT NULL COMMENT '丛书', `isbn` varchar(128) DEFAULT NULL COMMENT 'ISBN', `score` varchar(128) DEFAULT NULL COMMENT '评分', `number` int(10) unsigned DEFAULT NULL COMMENT '评论人数', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8
]
doubandb
,并进入数据库中创建books
数据表CREATE TABLE `books` ( `id` bigint(20) unsigned NOT NULL COMMENT 'ID号', `title` varchar(255) DEFAULT NULL COMMENT '书名', `author` varchar(64) DEFAULT NULL COMMENT '作者', `press` varchar(255) DEFAULT NULL COMMENT '出版社', `original` varchar(255) DEFAULT NULL COMMENT '原作名', `translator` varchar(128) DEFAULT NULL COMMENT '译者', `imprint` varchar(128) DEFAULT NULL COMMENT '出版年', `pages` int(10) unsigned DEFAULT NULL COMMENT '页数', `price` double(6,2) unsigned DEFAULT NULL COMMENT '定价', `binding` varchar(32) DEFAULT NULL COMMENT '装帧', `series` varchar(128) DEFAULT NULL COMMENT '丛书', `isbn` varchar(128) DEFAULT NULL COMMENT 'ISBN', `score` varchar(128) DEFAULT NULL COMMENT '评分', `number` int(10) unsigned DEFAULT NULL COMMENT '评论人数', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8
#使用requests加pyquery爬取所有豆瓣图书标签信息,并将信息储存于redis中 import requests from pyquery import PyQuery as pq import redis def main(): #使用requests爬取所有豆瓣图书标签信息 url = "https://book.douban.com/tag/?view=type&icn=index-sorttags-all" res = requests.get(url) print("status:%d" % res.status_code) html = res.content.decode('utf-8') # 使用Pyquery解析HTML文档 #print(html) doc = pq(html) #获取网页中所有豆瓣图书标签链接信息 items = doc("table.tagCol tr td a") # 指定Redis数据库信息 link = redis.StrictRedis(host='127.0.0.1', port=6379, db=0) #遍历封装数据并返回 for a in items.items(): #拼装tag的url地址信息 tag = a.attr.href #将信息以tag:start_urls写入到Redis中 link.lpush("book:tag_urls",tag) print("共计写入tag:%d个"%(len(items))) #主程序入口 if __name__ == '__main__': main()
python load_tag_url.py
scrapy startproject master
cd master
scrapy genspider book book.douban.com
master/item.py
文件import scrapy
class MasterItem(scrapy.Item):
# define the fields for your item here like:
url = scrapy.Field()
#pass
master/settings.py
文件... ROBOTSTXT_OBEY = False ... #下载器在下载同一个网站下一个页面前需要等待的时间。该选项可以用来限制爬取速度, 减轻服务器压力。同时也支持小数: DOWNLOAD_DELAY = 2 ... # Override the default request headers: DEFAULT_REQUEST_HEADERS = { # 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', # 'Accept-Language': 'en', 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0', } ... ITEM_PIPELINES = { 'master.pipelines.MasterPipeline': 300, } ... # 指定使用scrapy-redis的去重 DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter' # 指定使用scrapy-redis的调度器 SCHEDULER = "scrapy_redis.scheduler.Scheduler" # 在redis中保持scrapy-redis用到的各个队列,从而允许暂停和暂停后恢复,也就是不清理redis queues SCHEDULER_PERSIST = True # 指定排序爬取地址时使用的队列, # 默认的 按优先级排序(Scrapy默认),由sorted set实现的一种非FIFO、LIFO方式。 SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.SpiderPriorityQueue' # REDIS_URL = 'redis://localhost:6379' # 一般情况可以省去 REDIS_HOST = 'localhost' # 也可以根据情况改成 localhost REDIS_PORT = 6379
master/spiders/book.py
文件# -*- coding: utf-8 -*- import scrapy from master.items import MasterItem from scrapy import Request from urllib.parse import quote import redis,re,time,random class BookSpider(scrapy.Spider): name = 'master_book' allowed_domains = ['book.douban.com'] base_url = 'https://book.douban.com' def start_requests(self): ''' 从redis中获取,并爬取标签对应的网页信息 ''' r = redis.Redis(host=self.settings.get("REDIS_HOST"), port=self.settings.get("REDIS_PORT"), decode_responses=True) while r.llen('book:tag_urls'): tag = r.lpop('book:tag_urls') url = self.base_url + quote(tag) yield Request(url=url, callback=self.parse,dont_filter=True) def parse(self, response): ''' 解析每页的图书详情的url地址信息 ''' print(response.url) lists = response.css('#subject_list ul li.subject-item a.nbg::attr(href)').extract() if lists: for i in lists: item = MasterItem() item['url'] = i yield item #获取下一页的url地址 next_url = response.css("span.next a::attr(href)").extract_first() #判断若不是最后一页 if next_url: url = response.urljoin(next_url) #构造下一页招聘列表信息的爬取 yield scrapy.Request(url=url,callback=self.parse)
master/pipelines.py
文件import redis,re class MasterPipeline(object): def __init__(self,host,port): #连接redis数据库 self.r = redis.Redis(host=host, port=port, decode_responses=True) @classmethod def from_crawler(cls,crawler): '''注入实例化对象(传入参数)''' return cls( host = crawler.settings.get("REDIS_HOST"), port = crawler.settings.get("REDIS_PORT"), ) def process_item(self, item, spider): #使用正则判断url地址是否有效,并写入redis。 bookid = re.findall("book.douban.com/subject/([0-9]+)/",item['url']) if bookid: if self.r.sadd('books:id',bookid[0]): self.r.lpush('bookspider:start_urls', item['url']) else: self.r.lpush('bookspider:no_urls', item['url'])
scarpy crawl master_book
scrapy startproject salve
cd salve
scrapy genspider book book.douban.com
salve/item.py
文件import scrapy class BookItem(scrapy.Item): # define the fields for your item here like: id = scrapy.Field() #ID号 title = scrapy.Field() #书名 author = scrapy.Field() #作者 press = scrapy.Field() #出版社 original = scrapy.Field() #原作名 translator = scrapy.Field()#译者 imprint = scrapy.Field() #出版年 pages = scrapy.Field() #页数 price = scrapy.Field() #定价 binding = scrapy.Field() #装帧 series = scrapy.Field() #丛书 isbn = scrapy.Field() #ISBN score = scrapy.Field() #评分 number = scrapy.Field() #评论人数 #pass
salve/settings.py
文件BOT_NAME = 'slave' SPIDER_MODULES = ['slave.spiders'] NEWSPIDER_MODULE = 'slave.spiders' # Obey robots.txt rules ROBOTSTXT_OBEY = False ... # Override the default request headers: DEFAULT_REQUEST_HEADERS = { # 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', # 'Accept-Language': 'en', 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0', } ... ITEM_PIPELINES = { #'slave.pipelines.SlavePipeline': 300, 'scrapy_redis.pipelines.RedisPipeline': 400, } ... # 指定使用scrapy-redis的去重 DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter' # 指定使用scrapy-redis的调度器 SCHEDULER = "scrapy_redis.scheduler.Scheduler" # 在redis中保持scrapy-redis用到的各个队列,从而允许暂停和暂停后恢复,也就是不清理redis queues SCHEDULER_PERSIST = True # 指定排序爬取地址时使用的队列, # 默认的 按优先级排序(Scrapy默认),由sorted set实现的一种非FIFO、LIFO方式。 SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.SpiderPriorityQueue' # REDIS_URL = 'redis://localhost:6379' # 一般情况可以省去 REDIS_HOST = 'localhost' # 也可以根据情况改成 localhost REDIS_PORT = 6379
salve/spiders/book.py
文件# -*- coding: utf-8 -*- import scrapy,re from slave.items import BookItem from scrapy_redis.spiders import RedisSpider class BookSpider(RedisSpider): name = 'slave_book' #allowed_domains = ['book.douban.com'] #start_urls = ['http://book.douban.com/'] redis_key = "bookspider:start_urls" def __init__(self, *args, **kwargs): # Dynamically define the allowed domains list. domain = kwargs.pop('domain', '') self.allowed_domains = filter(None, domain.split(',')) super(BookSpider, self).__init__(*args, **kwargs) def parse(self, response): print("======================",response.status) item = BookItem() vo = response.css("#wrapper") item['id'] = vo.re_first('id="collect_form_([0-9]+)"') #ID号 item['title'] = vo.css("h1 span::text").extract_first() #书名 #使用正则获取里面的info里面的图书信息 info = vo.css("#info").extract_first() #print(info) authors = re.search('<span.*?作者.*?</span>(.*?)<br>',info,re.S).group(1) item['author'] = "、".join(re.findall('<a.*?>(.*?)</a>',authors,re.S)) #作者 item['press'] = " ".join(re.findall('<span.*?出版社:</span>\s*(.*?)<br>',info)) #出版社 item['original'] = " ".join(re.findall('<span.*?原作名:</span>\s*(.*?)<br>',info)) #原作名 yz = re.search('<span.*?译者.*?</span>(.*?)<br>',info,re.S) if yz: item['translator'] = "、".join(re.findall('<a.*?>(.*?)</a>',yz.group(1),re.S)) #译者 else: item['translator'] = "" item['imprint'] = re.search('<span.*?出版年:</span>\s*([0-9\-]+)<br>',info).group(1) #出版年 item['pages'] = re.search('<span.*?页数:</span>\s*([0-9]+)<br>',info).group(1) #页数 item['price'] = re.search('<span.*?定价:</span>.*?([0-9\.]+)元?<br>',info).group(1) #定价 item['binding'] = " ".join(re.findall('<span.*?装帧:</span>\s*(.*?)<br>',info,re.S)) #装帧 item['series'] = " ".join(re.findall('<span.*?丛书:</span>.*?<a .*?>(.*?)</a><br>',info,re.S)) #丛书 item['isbn'] = re.search('<span.*?ISBN:</span>\s*([0-9]+)<br>',info).group(1) #ISBN item['score'] = vo.css("strong.rating_num::text").extract_first().strip() #评分 item['number'] = vo.css("a.rating_people span::text").extract_first() #评论人数 #print(item) yield item
salve/pipelines.py
文件class SlavePipeline(object):
def process_item(self, item, spider):
return item
# 在spider目录下和book.py在一起。
scrapy runspider book.py
#将Redis中的Item信息遍历写入到数据库中 import json import redis import pymysql def main(): # 指定Redis数据库信息 rediscli = redis.StrictRedis(host='127.0.0.1', port=6379, db=0) # 指定MySQL数据库信息 db = pymysql.connect(host="localhost",user="root",password="",db="doubandb",charset="utf8") #使用cursor()方法创建一个游标对象cursor cursor = db.cursor() while True: # FIFO模式为 blpop,LIFO模式为 brpop,获取键值 source, data = rediscli.blpop(["book:items"]) print(source) try: item = json.loads(data) #组装sql语句 dd = dict(item) keys = ','.join(dd.keys()) values=','.join(['%s']*len(dd)) sql = "insert into books(%s) values(%s)"%(keys,values) #指定参数,并执行sql添加 cursor.execute(sql,tuple(dd.values())) #事务提交 db.commit() print("写入信息成功:",dd['id']) except Exception as err: #事务回滚 db.rollback() print("SQL执行错误,原因:",err) #主程序入口 if __name__ == '__main__': main()
# 创建项目框架myweb $ django-admin startproject myweb $ cd myweb # 在项目中创建一个web应用 $ python3 manage.py startapp web # 创建模板目录 $ mkdir templates $ mkdir templates/web $ cd .. $ tree myweb myweb ├── myweb │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── manage.py ├── web │ ├── admin.py │ ├── apps.py │ ├── __init__.py │ ├── models.py │ ├── tests.py │ └── views.py └── templates └── web
import pymysql
pymysql.install_as_MySQLdb()
... #配置自己的服务器IP地址 ALLOWED_HOSTS = ['*'] ... #添加自己应用 INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'web', ] ... # 配置模板路径信息 TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR,'templates')], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] ... # 数据库连接配置 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'doubandb', 'USER': 'root', 'PASSWORD': '', 'HOST': 'localhost', 'PORT': '3306', } ...
from django.db import models #图书信息模型 class Books(models.Model): title = models.CharField(max_length=255) #书名 author = models.CharField(max_length=64) #作者 press = models.CharField(max_length=255) #出版社 original = models.CharField(max_length=255)#原作名 translator = models.CharField(max_length=128)#译者 imprint = models.CharField(max_length=128)#出版年 pages = models.IntegerField(default=0)#页数 price = models.FloatField() #定价 binding = models.CharField(max_length=32) #装帧 series = models.CharField(max_length=128) #丛书 isbn = models.CharField(max_length=128) #ISBN score = models.CharField(max_length=128) #评分 number = models.IntegerField(default=0) #评论人数 class Meta: db_table = "books" # 更改表名
from django.conf.urls import url,include
urlpatterns = [
url(r'^',include('web.urls')),
]
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.index, name="index"),
url(r'^/$', views.index, name="index"),
]
from django.shortcuts import render from django.core.paginator import Paginator from web.models import Books # Create your views here. def index(request): #获取商品信息查询对象 mod = Books.objects list = mod.filter() #执行分页处理 pIndex = int(request.GET.get("p",1)) page = Paginator(list,50) #以50条每页创建分页对象 maxpages = page.num_pages #最大页数 #判断页数是否越界 if pIndex > maxpages: pIndex = maxpages if pIndex < 1: pIndex = 1 list2 = page.page(pIndex) #当前页数据 plist = page.page_range #页码数列表 #封装信息加载模板输出 context = {"booklist":list2,'plist':plist,'pIndex':pIndex,'maxpages':maxpages} return render(request,"web/index.html",context)
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>浏览图书信息</title> <style type="text/css"> table{font-size:13px;line-height:25px;border-collapse: collapse;} table,table tr th, table tr td { border:1px solid #dddddd; } </style> </head> <body> <center> <h2>浏览图书信息</h2> <table width="95%"> <tr style="background-color:#ddeedd;"> <th>ID号</th> <th>标题</th> <th>作者</th> <th>出版社</th> <th>出版年</th> <th>单价</th> <th>评分</th> </tr> {% for book in booklist %} <tr> <td>{{ book.id }}</td> <td>{{ book.title }}</td> <td>{{ book.author }}</td> <td>{{ book.press }}</td> <td>{{ book.imprint }}</td> <td>{{ book.price }}</td> <td>{{ book.score }}</td> </tr> {% endfor %} </table> <p> {% for pindex in plist %} {% if pIndex == pindex %} {{pindex}} {% else %} <a href="{% url 'index'%}?p={{ pindex }}">{{ pindex }}</a> {% endif %} {% endfor %} </center> </body> </html>
$ python manage.py runserver
使用浏览器访问测试
]
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。