windows系统在命令行模式下输入pip install scrapy
命令即可。
scrapy startproject demo
,该命令建立一个名称为demo的scrapy项目。server.py
程序,如下:import flask
app=flask.Flask(__name__)
@app.route("/")
def index():
return "测试scrapy"
if __name__ == "__main__":
app.run()
MySpider.py
,程序如下:import scrapy
class MySpider(scrapy.Spider):
name="mySpider"
def start_request(self):
url = "http://127.0.0.1:5000"
yield scrapy.Request(url=url,callback=self.parse)
def parse(self,response):
print(response.url)
data=response.body.decode()
print(data)
run.py
,程序如下:from scrapy import cmdline
cmdline.execute("scrapy crawl mySpider -s LOG_ENABLED=False".split())
run.py
,可以看到结果:http://127.0.0.1:5000
测试scrapy
由此可见程序MySpider.py是访问了我们自己的Web网站并爬取了网站的网页。下面来分析这个程序:
(1). import scrapy
引入scrapy程序包,这个包中有一个请求对象Request与一个响应对象Response类。
(2).
class MySpider(scrapy.Spider):
name = "mySpider"
任何一个爬虫程序类都继承与scrapy.Spider
类,任何一个爬虫程序都有一个名字,这个名字在整个爬虫项目是唯一的,此项目中的爬虫名字为“mySpider”。
(3)
def start_request(self):
url = "http://127.0.0.1:5000"
yield scrapy.Request(url=url,callback=self.parse)
这个地址url是爬虫程序的入口地址,这个start_request函数是程序的入口函数。程序开始时确定要爬取的网站地址,然后建立一个scrapy.Request请求类,向这个类提供url参数,指明要爬取的网页地址。爬取网页完成后就执行默认的回调函数parse。
scrapy的执行过程是异步进行的,即指定一个url网址开始爬取数据时,程序不用一直等待这个网站的响应,如果网站迟迟不响应,那么整个程序就卡死了。但是scrapy提供一个回调函数,网站什么时候响应就什么时候调用这个回调函数,这样有利于对响应时间很长的网站的爬取。
(4)
def parse(self,response):
print(response.url)
data=response.body.decode()
print(data)
回调函数parse包含一个scrapy.Request类的对象response,它是网站响应的一切信息,其中response.url就是网站的网址,response.body是网站响应的二进制数据,即网页的内容。通过decode()解码后变成字符串,就可以用print输出了。
(5) run.py
的作用
程序MySpider.py
只是一个类,不能单独执行,要执行这个爬虫程序就必须使用scrapy中专门的命令scrapy crawl。可以在命令窗口,在d:\example\demo\demo中执行命令:scrapy crawl mySpider -s LOG_ENABLED=False
,那么就可以看到执行后的结果。注:其中mySpider
指爬虫程序的名称,后面的参数是不现实调试信息。但是由于窗口的切换比较繁琐,因此设计一个python的run.py
,专门用来执行MySpider.py
程序。
在程序中使用了入口函数:
def start_request(self):
url = "http://127.0.0.1:5000"
yield scrapy.Request(url=url,callback=self.parse)
实际上这个函数可以用start_urls的入口地址来代替:
start_urls = "http://127.0.0.1:5000"
入口地址可有多个,因此start_urls是一个列表。入口函数与入口地址的作用是一样的。
def fun():
s = ['a','b','c']
for x in s:
yield x
print("fun End")
f = fun()
print(f)
for e in f:
print(e)
程序结果:
a
b
c
fun End
由此可见,fun返回一个generator的对象,这种对象包含一系列的元素,可以使用for循环提取,执行循环的过程如下:
for e in f:
print(e)
#从scrapy中引入Selector类,这个类就是选择查找类
from scrapy.selector import Selector
htmlText='''
Harry Potter
29.99
Learning XML
39.99
'''
selector=Selector(text=htmlText)
#查看selector的类型
print(type(selector))
print(selector)
#在文档中查找所有的元素,其中“//”表示文档中的任何位置。
s=selector.xpath("//title")
print(type(s))
print(s)
程序结果:
\n\n\n\t
[Harry Potter '>, Learning XML'>]
(1).selector=Selector(text=htmlText)
使用htmlText的文字建立Selector类,就是装载HTML文档,文档装载后就形成一个Selector对象,就可以使用xpath查找元素。
(2). selector.xpath("//tagName")
表示在文档中搜索
的tags,形成一个Selector的列表。
由此可见一般selector搜索一个的HTML元素的方法是:
selector.xpath("//tagName")
在装载HTML文档后selector=Selector(text=htmlText)
得到的selector是对应全文档顶层的元素的,其中"//"表示全文档搜索,结果是一个Selector的列表,哪怕只有一个元素也成一个列表。
selector.xpath("//body")搜索到元素,结果是一个Selector的列表,包含一个Selector元素;
selector.xpath("//title")
搜索到两个
元素,结果是Selector的列表,包含2个Selector元素;
selector.xpath("//book")
搜索到两个元素,结果是Selector的列表,包含2个Selector元素;
selector.xpath("//bookstore/book")
搜索
下一级的
元素,找到2个;selector.xpath("//body/book")
搜索
的下一级
元素,结果为空;selector.xpath("//body//book")
搜索
下的
元素,找到2个;selector.xpath("/body//book")
搜索文档下一级的
下的
元素,找结果为空,因为文档的下一级是
元素,而不是
;selector.xpath("/html/body//book")
或者selector.xpath("/html//book")
搜索
元素,找到2个;selector.xpath("//book/title")
搜索文档中所有
下一级的
元素,找到2个,结果跟selector.xpath("//title")
、selector.xpath("//bookstore//title")
一样;selector.xpath("//book//price")
与selector.xpath("//price")
结果一样,都是找到2个
元素。from scrapy.selector import Selector
htmlText='''
Novel
Harry Potter
29.99
TextBook
Learning XML
39.99
'''
selector=Selector(text=htmlText)
s=selector.xpath("//book").xpath("./title")
for e in s:
print(e)
程序结果:
Novel'>
Harry Potter'>
TextBook'>
Learning XML'>
selector.xpath("//book")
首先搜索到文档中所有
元素,总共有2个,然后再次调用xpath("./title")
,就是从当前元素
开始往下一级搜索
,每个
都找到2个
,因此结果有4个
。xpath
连续调用时不指定是从前一个xpath
的结果元素开始的,那么默认是从全文档开始的,结果就会不一样,例如:s=selector.xpath("//book").xpath("/title")
结果就是空的,因为后面的xpath("/title")
从文档开始搜索
。s=selector.xpath("//book").xpath("//title")
结果有10个元素,因为每个
都驱动xpath<"//title">
在全文档搜索
元素,每次都搜索到5个元素。如果xpath返回的是一个Selector对象列表:
xpath返回的是一个单一的Selector对象:
from scrapy.selector import Selector
htmlText='''
Harry Potter
29.99
learning-XML
39.98
'''
selector=Selector(text=htmlText)
s=selector.xpath("//book/price")
print(type(s),s)
s=selector.xpath("//book/price").extract()
print(type(s),s)
s=selector.xpath("//book/price").extract_first()
print(type(s),s)
程序结果:
[, ]
['29.99 ', '39.98 ']
29.99
由此可见:s=selector.xpath("//book/price")
得到的是SelectorList列表;
s=selector.xpath("//book/price").extract()
得到的是
元素的Selector对象对应的
元素的文本组成的列表,即[
;s=selector.xpath("//book/price").extract_first()
得到的是
元素的文本组成的列表的第一个元素,是一个文本,即
。
selector=Selector(text=htmlText)
s=selector.xpath("//book/@id")
print(s)
print(s.extract())
for e in s:
print(e.extract())
程序结果:
[, ]
['b1', 'b2']
b1
b2
由此可见:s=selector.xpath("//book/@id")
结果是2个
的id属性组成的SelectorList列表,即属性也是一个Selector对象;print(s.extract())
结果是的id属性的两个Selector对象的属性文本值的列表,即['b1','b2']
;e.extract()
获取对象e的属性值。
selector=Selector(text=htmlText)
s=selector.xpath("//book/title/text()")
print(s)
print(s.extract())
for e in s:
print(e.extract())
程序结果:
[,]
['Harry Potter','Learning-XML']
Harry Potter
Learning-XML
由此可见:s=selector.xpath("//book/title/text()")
结果也是SelectorList列表,即文本也是一个节点;print(s.extract())
结果是文本节点的字符串值的列表,即['Harry Potter','Learning-XML']
;最后的for语句,每个e是一个Selector对象,因此extract()获取对象的属性值。值得注意的是如果一个element的元素包含的文本不是单一的文本,那么可能会产生多个文本值。
"tag[condition]"
来限定一个tag元素,其中condition是由这个tag的属性、文本等计算出的一个逻辑值。如果有多个条件,可以写成:"tag[condition1][condition2]...[conditionN]"
或者"tag[condition1] and [condition2] and ... and [conditionN]"
。"*"
代表任何Element节点,不包括Text、Comment的节点。"@*"
代表任何属性。"element/parent::*"
选择element的父节点,这个节点只有一个。如果写成"element/parent::tag"
,就是指定element的tag父节点,除非element的父节点正好是tag节点,不然就为None。"element/following-sibling::*"
搜索element后面的同级的所有兄弟节点,使用"element/following-sibling::*[position()=1]"
搜索element后面的同级的第一个兄弟节点。"element/preceding-sibling::*"
搜索element前面的同级的所有兄弟节点,使用"element/preceding-sibling::*[position()=1]"
搜索element前面的同级的第一个兄弟节点。使用scrapy startproject demo
语句创建一个demo的scrapy项目,在项目中创建一个server.py
文件,用来编写网站内容,这个网站有一个网页,返回基本计算机教材,flask程序如下:
import flask
app = flask.Flask(__name__)
@app.route("/")
def index():
html="""
Python程序设计
James
人民邮电出版社
Java程序设计
Robert
人民邮电出版社
MySQL数据库
Steven
高等教育出版社
"""
return html
if __name__=="__main__":
app.run()
程序爬取的数据是多本教材,每本教材有名称与作者,因此要建立一个教材的类,类中包括教材名称title,作者author与出版社publisher。在demo\demo目录下有一个items.py文件就是用来设计数据项目类的,打开文件,并改成如下形式:
import scrapy
class BookItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
title = scrapy.Field()
author = scrapy.Field()
publisher = scrapy.Field()
其中BookItem是我们设计的教材项目类,这个类必须从scrapy.Itrm类继承,在类中定义教材的字段项目,每个字段项目都是一个scrapy.Field对象,定义了3个字段项目,分别用来存储title、author以及publisher。注:可以通过item[“title”]、item[“author”]、item[“publisher”]来获取或设置各个字段的值。
在demo\demo\spider目录下新建myspider.py
文件,编写程序如下:
import scrapy
from scrapy import Selector
#从demo文件夹的items.py文件中引入BookItem类的定义
from demo.items import BookItem
class MySpider(scrapy.Spider):
name = "mySpider"
start_urls = ["http://127.0.0.1:5000"]
def parse(self, response):
try:
#得到网站数据并建立Selector对象,搜索到所有的节点的元素
data = response.body.decode()
selector = Selector(text=data)
books = selector.xpath("//book")
for book in books:
item = BookItem()
#对于每个节点,在它的下面搜索、、节点,并取出它们的文本,组成一个BookItem对象
item['title'] = book.xpath("./title/text()").extract_first()
item['author'] = book.xpath("./author/text()").extract_first()
item['publisher'] = book.xpath("./publisher/text()").extract_first()
#向上一级调用函数返回
yield item
#向上一级调用函数返回,scrapy把BookItem对象推送给与items.py同目录下的pipeline.py文件中的数据管道执行类取处理数据
except Exception as e:
print(e)
在scrapy框架中的demo\demo目录下有一个pipeline.py文件,用来数据管道处理文件,打开这个文件可以看到一个默认的管道类,修改并设计管道程序如下:
class BookPipeline(object):
count=0
def process_item(self, item, spider):
BookPipeline.count+=1
try:
if BookPipeline.count==1:
fobj=open("books.txt","wt")
else:
fobj=open("books.txt","at")
print(item["title"],item["author"],item["publisher"])
fobj.write(item["title"]+","+item["author"]+","+item["publisher"]+"\n")
fobj.close()
except Exception as e:
print(e)
return item
说明:这个类,命名为BookPipeline,它继承自object类,类中最重要的函数是process_item函数,scrapy爬取数据开始会建立一个BookPipeline类对象,然后每爬取一个数据类BookItem项目item,mySpider程序就会把这个对象推送给BookPipeline对象,同时调用process_item函数一次。process_item函数的参数中的item就是推送来的数据,于是就可以在这个函数中保存完整的爬取数据了。
mySpider程序执行后每爬取一个item项目是如何推送给BookPipeline对象并调用process_item函数的呢?前提是必须设置一个通道。在demo文件夹中一个settings.py的设置文件,打开这个文件可以看到很多设置项目,大部分都是用#注释的语句,找到 语句ITEM_PIPLINES的项目,把它设置如下形式:
# Configure item pipelines
# See https://doc.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
'demo.pipelines.BookPipeline': 300,
}
其中ITEM_PIPLINES是一个字典,把关键字改成 ‘demo.pipelines.BookPipeline’,而BookPipelines就是在pipelines.py文件中设计的数据管道类的名称,后面的300是一个默认的整数,可以是任何整数。这样设置后就可以连通爬虫mySpider程序通过yield返回的每项数据推送给pipelines.py程序的BookPipeline类,并执行process_item函数了。
在demo中创建run.py文件,内容见上。运行run.py可得到一个book.txt文件。打开文件,则可见爬取存储数据是否成功。
计算机
数据库
返回首页
程序设计
返回首页
计算机网络
返回首页
MySQL数据库
返回首页
Python程序设计
返回首页
Java程序设计
返回首页
flask.py
import flask
import os
app = flask.Flask(__name__)
def getFile(fileName):
data = b""
if os.path.exists(fileName):
fobj = open(fileName,"rb")
data = fobj.read()
fobj.close()
return data
@app.route("/")
def index():
return getFile("books.html")
@app.route("/")
def process(section):
data = ""
if section != "":
data = getFile(section)
return data
if __name__ == "__main__":
app.run()
程序如下
import scrapy
from scrapy import Selector
class MySpider(scrapy.Spider):
name = "mySpider"
#入口地址,访问这个网址成功后会回调parse函数
start_urls = ["http://127.0.0.1:5000"]
#定义一个回调函数,该函数的response对象包含了网站返回的信息
def parse(self, response):
try:
print(response.url)
#网站返回的response.body的二进制函数,要decode转为文本,然后建立Selector对象
data = response.body.decode()
selector = Selector(text=data)
#获取网页中的标题的文本
print(selector.xpath("//h3/text()").extract_first())
#获取所有的链接的href值,组成links列表
links = selector.xpath("//a/@href").extract()
for link in links:
url = response.urljoin(link)
yield scrapy.Request(url,callback=self.parse)
except Exception as e:
print(e)
运行run.py程序,得到如下结果:
http://127.0.0.1:5000
计算机
http://127.0.0.1:5000/database.html
数据库
http://127.0.0.1:5000/network.html
计算机网络
http://127.0.0.1:5000/program.html
程序设计
http://127.0.0.1:5000/books.html
计算机
http://127.0.0.1:5000/mysql.html
MySQL数据库
http://127.0.0.1:5000/python.html
Python程序设计
http://127.0.0.1:5000/java.html
Java程序设计
说明:scrapy会自动筛选已经访问过的网站,访问links的每个link,通过urljoin函数与response.url地址组合成完整的url地址,再次建立Resquest对象,回调函数仍然为parse,即这个parse函数会被递归调用。其中使用了yield语句返回每个Request对象。