既然要爬取别人的网站的数据,就存在安全性问题。关于网络爬虫的建议,每个网站可能会有robots.txt
文件,这个文件一般放在网站的根目录下下。该文件建议了网站的哪些目录允许爬取,哪些目录不允许爬取。当然这个Robots协议只是一种建议,它没有法律的约束力,但是作为一个遵纪守法的良好公民还是要遵循它的建议。
可以通过网站名/robots.txt
来查看Robots协议,例如:
注意,在上面的robots.txt文件中的User-agent指明的爬取的对象,也就是说第一部分的意思就是:对于百度的爬取,哪些是允许Allow的,哪些是不允许Disallow的。
当然有些网站没有robots.txt
的话会出现404,那就说明开发人员没有设定哪些可爬哪些不可爬,我们默认就全部可爬。
Requests
:最友好的网络爬虫库之一
requests
是基于Python开发的HTTP库
…
例如:爬取搜狐页面上的数据
import requests
url='http://www.sohu.com'
response=requests.get(url)
#当输出的文本中出现中文乱码时需要设置encoding属性
response.encoding='utf-8'
text=response.text
print(text)
另外,还可能出现的一种情况是:利用百度搜索"xxx",爬取搜索页,例如百度搜索库里,其中的wd
就是指关键字库里,就相当于是一个键值对,键名是wd
,值名是库里
:
对于这种类型的爬取,如果写成:
import requests
url='http://www.baidu.com/s?'
params={
'wd':'库里'
}
response=requests.get(url,params=params)
response.encoding='utf-8'
text=response.text
print(text)
这样是无法显示出正确的页面的,在Pycharm控制台按
Ctrl+F
,并查询关键字:库里是查询不到的。会弹出类似于百度安全验证的信息,这就是反爬虫机制,因为服务器检测到是一个Python程序在爬取它的数据而不是正常的网页搜索,那么要反反爬虫,就需要添加伪装头信息,让服务器以为我就是网页搜索,不是Python爬虫:
import requests
#下面的com后面的/s?是用来分隔地址和参数的,在网页地址栏中就能看到
url='http://www.baidu.com/s?'
params={
'wd':'库里'
}
headers={
'Accept': 'text/javascript, application/javascript, application/ecmascript, application/x-ecmascript, */*; q=0.01Accept-Encoding: gzip, deflate,Accept-Language: zh-CN,zh;q=0.9',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.122 Safari/537.36'
}
#Accept:能接收的文件类型;User-agent:请求的发送者
response=requests.get(url,params=params,headers=headers)
response.encoding='utf-8'
text=response.text
print(text)
这样爬取到的内容就是我们百度搜索页面搜索关键字得到的页面
响应客户端的请求,动态生成响应,包括状态码,网页的内容等。有以下几种响应属性:
例如,content的使用方法:
...
t=response.content
print(t.decode())
#decode()中默认的参数,即转码形式就是'utf-8'
爬取到网页的数据之后,可以借助网页解析器从网页中解析和提取出有价值的信息,或者是新的URL列表。前面通过requests库得到的仅仅是文本形式的网页源码。
通过.标签名
就可以获取到标签的所有值,如果有多个标签只能获取到第一个元素,常用的标签名和属性有:
属性 | 描述 |
---|---|
head | HTML页面的 内容 |
title | HTML页面标题,在 之中,由 标记 |
body | HTML页面的 内容 |
p | HTML页面的第一个 内容 |
strings | HTML页面所有呈现在Web上的字符串,即标签的内容 |
stripped_strings | HTML页面所有呈现在Web上的非空格字符串 |
每一个Tag标签在beautifulsoup4库中也是一个对象,称为Tag对象:
属性 | 描述 |
---|---|
name | 字符串,标签的名字,比如div |
attrs | 字典,包含了原来页面Tag所有的属性,比如href,class |
contents | 列表,这个Tag下所有子Tag的内容 |
string | 字符串,Tag所包含的文本,网页中真实的文字 |
另外,还有一个find_all
方法可以根据参数找到对应标签,返回列表类型,具体使用方法为:
BeautifulSoup.find_all(name,attrs,recursive,string,limit)
参数 | 描述 |
---|---|
name | 按照Tag标签名字检索,名字用字符串形式表示,例如div,li |
attrs | 按照Tag标签属性值检索,需要列出属性名称和值,采用JSON表示 |
recursive | 设置查找层次,只查找当前标签的下一层时使用recursive =False |
string | 按照关键字检索string属性内容,采用string=开始 |
limit | 返回结果的个数,比如你要取前几名就可以用到这个参数,默认返回全部结果 |
例如:
from bs4 import BeautifulSoup
#准备HTML字符串数据
html='''
it is title
百度
hao123
搜狐
'''
bs=BeautifulSoup(html)
#在运行程序时可能会出现警告,因此需要加上一个解析器,就是下面的'html.parser'
#bs=BeautifulSoup(html,'html.parser')
#bs.prettify()就是将整个网页按照有缩进的格式进行输出
print(bs.prettify())
#输出页面的header信息
print(bs.header)
#获取第一个a标签的各种属性,名字,属性,内容,文本
print(bs.a.name)
print(bs.a.attrs)
print(bs.a.contents)
print(bs.a.string)
print(bs.html.head.title.string)
new_lt=bs.find_all('a')
for lt in new_lt:
print(lt.string)
print(bs.find('a',{
'href':'http://www.baudu.com'}))
运行结果如下所示:
<html>
<head>
<title>
it is title
</title>
</head>
<body>
<a class="mnav" href="http://www.baudu.com">
百度
</a>
<a class="mnav" href="http://www.hao123.com">
hao123
</a>
<a class="mnav" href="http://www.sohu.com">
搜狐
</a>
</body>
</html>
a
{
'class': ['mnav'], 'href': 'http://www.baudu.com'}
['百度']
百度
it is title
百度
hao123
搜狐
<a class="mnav" href="http://www.baudu.com">百度</a>
来一个实战:利用网络爬虫爬取全国最好大学的排名:
中国最好大学排名2019
分析上图后可以知道,表格的每一行就是一个大学,每一行对应的标签元素是
,在每一行中的
标签中放的是该学校的排名,名称,位置,总分等等信息,每一行有多个
标签。
然后直接上代码:
import requests
from bs4 import BeautifulSoup
import pandas as pd
allUniv=[]
def getHtmlText(url):
r=requests.get(url)
r.encoding='utf-8'
return r.text
def fillUnivList(bs):
data_lt=bs.find_all('tr')
#一个t就是其中的一行也就是一个大学
for t in data_lt:
tdd=t.find_all('td',limit=4)
single_lt=[]
for x in tdd:
#每一个tdd中除了内容还包含标签信息,因此需要.string来获取内容
single_lt.append(x.string)
allUniv.append(single_lt)
def saveToExcel(num):
x=pd.DataFrame(allUniv[1:num],columns=['排名','学校','省市','总分'])
print(x)
x.to_csv('a1.csv',index=False)
x.to_excel('a1.xlsx',index=False)
def main(num):
url='http://www.zuihaodaxue.com/zuihaodaxuepaiming2019.html'
html=getHtmlText(url)
bs=BeautifulSoup(html,'html.parser')
fillUnivList(bs)
saveToExcel(num)
main(20)
最终运行结果:
a1.csv
排名,学校,省市,总分
1,清华大学,北京,94.6
2,北京大学,北京,76.5
3,浙江大学,浙江,72.9
4,上海交通大学,上海,72.1
5,复旦大学,上海,65.6
6,中国科学技术大学,安徽,60.9
7,华中科技大学,湖北,58.9
7,南京大学,江苏,58.9
9,中山大学,广东,58.2
10,哈尔滨工业大学,黑龙江,56.7
11,北京航空航天大学,北京,56.3
12,武汉大学,湖北,56.2
13,同济大学,上海,55.7
14,西安交通大学,陕西,55.0
15,四川大学,四川,54.4
16,北京理工大学,北京,54.0
17,东南大学,江苏,53.6
18,南开大学,天津,52.8
19,天津大学,天津,52.3
获取到想要的html页面之后,数据提取的三种方式:Xpath语法,正则表达式和bs4库。关于这三种方法的比较:
解析方式 | 解析速度 | 困难程度 |
---|---|---|
Xpath | 快 | 中等 |
bs4 | 慢 | 容易 |
re(正则表达式) | 最快 | 困难 |
Xpath是一门在XML和HTML文档中查找信息的语言,可用来在XML和HTML在文档中对元素和属性进行遍历。就要按照一定规则来进行数据的获取,这种规则就叫做Xpath语法。
HTML页面是由标签构成的,这些标签像这个族谱一样排列有序。所有的HTML标签都有很强的层级关系,正是基于这种层次关系,Xpath语法能够选择出我们想要的数据。每个标签都可能有这这样的关系:xxx>>太爷爷>>爷爷>>爸爸>>儿子>>孙子
。
节点的选取:
表达式 | 描述 | 用法 | 说明 |
---|---|---|---|
nodename | 选取此节点的所有子节点 | div | 选取div下的所有标签 |
// | 从全局节点中选取节点 | //div | 选取整个HTML页面的div标签 |
/ | 选取某个节点下的节点 | //head/title | 选取head标签下的title标签 |
@ | 选取带某个属性的节点 | //div[@id] | 选择带有id属性的div标签 |
. | 当前节点下 | ./span | 选择当前节点下的span标签[代码中威力强大] |
在插件左侧输入选取语句右侧就会出现相应结果,同时选取的元素被被高亮显示(其实就是在元素增加了一个高亮的class样式)
谓语:
表达式 | 用法说明 |
---|---|
//head/meta[1],//head/meta[k] | 选择所有head下的第一个meta标签(下标从1开始),选择所有head下的第K个meta标签 |
//head/meta[last()] | 选择所有head下的最后一个meta标签 |
//head/meta[position()< 3] | 选择所有head下的前两个meta标签 |
//div[@id] | 选择带有id属性的div标签 |
//div[@id=‘u1’] | 选择所有拥有id=u1的div标签 |
补充,如果想要获取到标签中的文本内容,可以用
/text()
lxml库对html的字符串进行解析,将它还原为一个HTML页面,供Xpath语法进行数据提取。
字符串还原为html页面的方法为:
from lxml import etree
html=etree.HTML('text')
request
来获取到页面的字符串形式的文件lxml
将字符串文件转化为html的文件例如想要抓取百度首页的新闻等标签和对应的链接,打印输出并保存为csv文件:
from lxml import etree
import requests
import pandas as pd
url="https://www.baidu.com/"
headers={
'Accept': 'text/javascript, application/javascript, application/ecmascript, application/x-ecmascript, */*; q=0.01Accept-Encoding: gzip, deflate,Accept-Language: zh-CN,zh;q=0.9',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.122 Safari/537.36'
}
response=requests.get(url,headers=headers)
response.encoding='utf-8'
text=response.text
html=etree.HTML(text)
conttext=html.xpath('//div[@id="s-top-left"]/a/text()')
conthref=html.xpath('//div[@id="s-top-left"]/a/@href')
daa=zip(conttext,conthref)
datapd=pd.DataFrame(daa,columns=['栏目','超链接'])
print(datapd)
datapd.to_csv('百度首页爬取1.csv',index=False)
在上面的百度首页爬取时目标只有2个关键字,所以我们可以用
zip
函数将它们直接打包,但是当碰到需要爬取的数据3,4列甚至更多时,用zip
函数就比较麻烦。
再看另一个例子:爬取世界大学排名,打印输出并保存为csv文件,网站为:世界最好大学,代码如下:
from lxml import etree
import requests
import pandas as pd
url="http://www.zuihaodaxue.cn/ARWU2019.html"
headers={
'Accept': 'text/javascript, application/javascript, application/ecmascript, application/x-ecmascript, */*; q=0.01Accept-Encoding: gzip, deflate,Accept-Language: zh-CN,zh;q=0.9',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.122 Safari/537.36'
}
response=requests.get(url,headers=headers)
response.encoding='utf-8'
text=response.text
html=etree.HTML(text)
uni_level=html.xpath('//tbody/tr/td[1]/text()')
uni_name=html.xpath('//tbody/tr/td/a/text()')
uni_area=html.xpath('//tbody/tr//a/img/@src')
uni_img=[s1.replace('image/flag/','').replace('.png','')for s1 in uni_area]
print(uni_level)
print(uni_name)
print(uni_img)
# 当数据不止两列时可以用字典的方式传入
datapd=pd.DataFrame(data={
'世界排名':uni_level[:20],'学校名称':uni_name[:20],'所在国家/地区':uni_img[:20]})
datapd.to_csv('世界最好大学排名.csv',index=False)
经过一番缜密的分析,斗图啦这个网站的表情包均在最新表情这个专栏中,并且在该专栏中,每一页的地址都有固定格式及规律(地址栏中会标注页码):
因此只需要设置一个for循环将需要爬取的页面地址都存放在一个列表变量中即可。实现代码如下:
import requests
from lxml import etree
import urllib
PAGE_URL_LIST=[]
base_url="https://www.doutula.com/photo/list/?page="
#获取到所有页面的URL(这里暂时爬取100页)
for i in range(0,101):
x=base_url+str(i)
PAGE_URL_LIST.append(x)
#这里的pop方法可以直接取出元素并将它从列表中删除
page_url=PAGE_URL_LIST.pop()
#伪装请求头信息
headers={
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36'
}
response=requests.get(page_url,headers=headers)
response.encoding='utf-8'
text=response.text
html=etree.HTML(text)
img_list=html.xpath("//div[@class='page-content text-center']//img/@data-original")
for img in img_list:
#为了避免下载图片命名重复,直接获取到页面的图片名字保存
filename=img.split('/')[-1]
path='./image/'+filename
#img参数是地址,path参数是保存位置
urllib.request.urlretrieve(img,path)
在上面的实例中,如果需要爬取的页数很多,整个运行过程会变得很慢,因此需要用多线程来解决这个问题。所谓多线程,就是指同时执行多个不同的程序,它是利用标准库import threading
来实现的。常用的方法和函数有:
th=threading.Thread(target=fun,args=[...])
th.start()
th.join()
,主线程任务结束之后,进入阻塞状态,一直等待其它的子线程执行结束之后,主线程再终止。例如:
import threading
import time
def hello(x):
print("hello{}".format(x))
time.sleep(0.5)
def line_fun():
for x in range(10):
hello(x)
line_fun()
与用到线程的程序:
import threading
import time
def hello(x):
print("hello{}".format(x))
time.sleep(0.5)
def thread_fun():
for x in range(10):
th=threading.Thread(target=hello,args=[x])
th.start()
thread_fun()
对于前者(没有用到线程的程序)来讲,运行结果应该是
hello0—hello9
依次出现,且每个间隔0.5s
,而对于后者(用到线程的程序),hello0—hello9
几乎同时很快出现,因为前者仅仅用到了一个线程,因此每次输出需要排队等待0.5s
,而后者一共有10个线程,分别用于输出hello0—hello9
,因此它们是同时开始执行的,所以很快就输出全部。这个就是多线程的应用。有了多线程的应用,在许多项目中能够提高运行速度。
生产者不断的生产资源并放到资源池中,而消费者不断的消耗资源池中的资源。资源池在实际项目中可以理解为全局变量。如果没有应用多线程,那么假如某个消费者需要用到的资源大于资源池中的资源,那么它就必须停下来等待直到资源池中有足够的资源够它使用。假如应用了多线程,在同一时间内,多个线程可以同时生产资源,然后将资源放到资源池中,这样资源池中的资源量肯定比单个线程来的多,项目整体运行速度也会更快。
下面来看一个例子:
import threading
g_num=1
def test1():
global g_num
for i in range(1000000):
g_num+=1
print("test1={}".format(g_num))
def test2():
global g_num
for i in range(1000000):
g_num+=1
print("test2={}".format(g_num))
def main():
print("g_num原始值为{}".format(g_num))
t1=threading.Thread(target=test1)
t1.start()
t2=threading.Thread(target=test2)
t2.start()
t1.join()
t2.join()
print("--- main end ---")
print("g_num现在的值为{}".format(g_num))
main()
test1()
和test2()
方法都是对全局变量g_num
进行+1
操作,分别循环1000000
次,t1线程执行test1,t2线程执行test2,这两个线程同步执行,按道理全部执行完之后结果应该是2000001,但是运行之后的结果却不是2000001:
并且每次运行得到的结果都不一样,而且这两个线程的执行先后也不一样,上图来看是test2先执行完。为什么每次运行得到的结果都不是预期中的2000001
呢?存在的问题就是:对资源池同时做生产者消费者操作时,数据会出错。就好比初始值10
,线程一要执行+3
操作,线程二要执行-5
操作,在不知道线程一和二哪个先执行的情况下,假如同时执行两个线程会得到13和5这两个结果就会出错,实际上正确的结果应该是8。
针对上面这种数据出错的情况,解决方法就是加入线程锁。
所谓线程锁就是某一线程对资源池操作时,加锁,禁止其它线程操作。也就是同一时间只允许一个线程对资源池进行操作。该线程对资源池操作完毕之后,解锁。
操作方法:
glock=threading.Lock()
glock.acquire()
glock.release()
将上述例子加上线程锁之后:
import threading
g_num=1
glock=threading.Lock()
def test1():
global g_num
for i in range(1000000):
glock.acquire()
g_num+=1
glock.release()
print("test1={}".format(g_num))
def test2():
global g_num
for i in range(1000000):
glock.acquire()
g_num+=1
glock.release()
print("test2={}".format(g_num))
def main():
print("g_num原始值为{}".format(g_num))
t1=threading.Thread(target=test1)
t1.start()
t2=threading.Thread(target=test2)
t2.start()
#t1.join()
#t2.join()
print("--- main end ---")
print("g_num现在的值为{}".format(g_num))
main()
将实例整个过程可以分为两个部分,生产者就是从网站上的页面获取到每张图片的下载地址,资源池里放的就是所有图片的下载地址,而消费者就是从资源池中获取下载地址,下载图片。这就将这个项目与多线程(生产者消费者模式)结合在了一起,可以提高整个项目的运行速度及效率。
具体代码如下:
import requests
from lxml import etree
import urllib
import threading
g=threading.Lock()
PAGE_URL_LIST=[]
FACE_URL_LIST=[]
base_url="https://www.doutula.com/photo/list/?page="
for i in range(0,101):
x=base_url+str(i)
PAGE_URL_LIST.append(x)
# 生产者
def p():
while True:
#这里需要判断一下是否已经把所有页面取完了
if len(PAGE_URL_LIST)==0:
break
else:
page_url = PAGE_URL_LIST.pop()
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36'
}
response = requests.get(page_url, headers=headers)
response.encoding = 'utf-8'
text = response.text
html = etree.HTML(text)
img_list = html.xpath("//div[@class='page-content text-center']//img/@data-original")
g.acquire()
for img_url in img_list:
FACE_URL_LIST.append(img_url)
g.release()
def c():
while True:
#这里需要判断资源池中的资源是否消耗完了
if len(FACE_URL_LIST)==0:
# 消费者消费太快就等一会,不是break
continue
else:
g.acquire()
face_url=FACE_URL_LIST.pop()
# 为了避免下载图片命名重复,直接获取到页面的图片名字保存
filename = face_url.split('/')[-1]
path = './image/' + filename
# img参数是地址,path参数是保存位置
urllib.request.urlretrieve(face_url,path)
g.release()
def main():
for i in range(5):
th1=threading.Thread(target=p)
th1.start()
for i in range(4):
th2=threading.Thread(target=c)
th2.start()
th1.join()
th2.join()
scrapy是基于Twisted的异步处理框架,是纯Python实现的爬虫框架。另外常用的Python爬虫框架有:Scrapy
,Crawley
,Portia
,Newspaper
。
网络爬虫的工作步骤可大致分为四步:
在数据解析的过程中做两件事情,第一就是获取到想要的数据,第二就是在解析的过程中如果碰到新的URL再返回给URL列表。
上面的工作流程图有一个缺点是:当中间某个模块出错的时候,其他模块都会受到影响。也就是各个模块之间的依赖性很高。为了解决这个问题,scrapy就引入了一个中间模块——协调员,来协调各个模块之间的工作。引入协调员之后,上图就变成了:
scrapy工作流程如下图:
1.新建项目:创建一个新的爬虫项目
在终端使用命令创建项目,命令格式为:scrapy startproject 项目名称
,创建完了之后项目格式应该如下图所示:
scrapy.cfg
是整个项目的配置文件,settings.py
是整个项目的各种设置,包括请求头,爬虫约定robotstxt等等:
pipelines.py
:数据管道,用来处理数据(保存数据,去重数据,产生新的URL送回到调度器)
,middlewares.py
:中间件,items.py
:实体文件,用来定义项目的实体,就比如我要建立一个数据库的表,就要先定义表中的字段名(姓名,性别,年龄…),_init_.py
:初始化文件。
2.明确目标:明确想要抓取的目标
实例:爬取赶集网上面的租房信息
对页面分析之后,想要获取的数据如下图所示:
然后编写items.py
,定义实体:
import scrapy
class MyspiderItem(scrapy.Item):
# 租房标题
title = scrapy.Field()
# 价格
price = scrapy.Field()
# 房型
h_type = scrapy.Field()
# 装修程度
dec = scrapy.Field()
3.制作爬虫:制作爬虫,开始爬取网页
第三步制作爬虫中又可以分为以下三步:
1.创建爬虫
终端输入命令:scrapy genspider 爬虫名称 "爬取域"
,注意要进入项目目录之后再输入。在本例中,可以输入scrapy genspider ganji ganji.com
,爬取域可以选择范围最大的主页面,这样还可以爬取到其它页面的数据。
爬虫创建好了之后,在spiders
文件夹下就会多了一个ganji.py
,它的文件格式为:
class GanjiSpider(scrapy.Spider):
# 爬虫的名称
name = 'ganji'
# 爬虫搜索的域名范围
allowed_domains = ['ganji.com']
# 这是爬取的起始URL,在这里可以修改成租房的那个页面
start_urls = ['http://hz.ganji.com/zufang/']
# parse方法:用于处理响应,1.解析提取的数据 2.提取新的URL
def parse(self, response):
pass
2.运行爬虫
使用命令:scrapy crawl 爬虫名称
,在输出的时候会附带很多其它日志信息,可以用命令scrapy crawl 爬虫名称 --nolog
运行爬虫之后就会执行parse方法
,第一次运行是可以的,但是运行多次之后就发现不行了,因为网站存在反爬机制,因此我们需要反反爬,在settings.py
中将USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36'
USER_AGENT这一段的注释给去掉,使之生效(具体的USER_AGENT的内容需要在网页上右键检查来获取)。
3.提取数据
整个ganji.py
解析网页,提取数据的代码如下:
import scrapy
from myspider.items import MyspiderItem
class GanjiSpider(scrapy.Spider):
name = 'ganji'
allowed_domains = ['ganji.com']
start_urls = ['http://hz.ganji.com/zufang/']
def parse(self, response):
# print(response,'-'*100)
divs=response.xpath("//div[@class='f-main-list']/div/div[position()>1]")
item = MyspiderItem()
# 遍历divs的时候,把要提取的数据放入事先定义好的数据模型中,导入数据模型文件
for div in divs:
# 默认返回的是selector的对象列表,可以使用extract()获取对象列表,extract_first()获取对象列表的第一个元素
item['title']=div.xpath("./dl/dd[1]/a/text()").extract_first()
price=div.xpath("./dl/dd[@class='dd-item info']/div/span/text()").extract()
item['price']=''.join(price)
item['h_type']=div.xpath("./dl/dd[@class='dd-item size']/span[1]/text()").extract_first()
item['dec']=div.xpath("./dl/dd[@class='dd-item size']/span[@class='last']/text()").extract_first()
# 为了避免有些户主没有提交装修信息,以及便于用户的直观了解,可以做一下转换
# item['dec']没有信息,可能是0也可能为空
if not item['dec'] or item['dec']==0:
item['dec']='暂无装修信息'
print(item)
yield item
# 与 return 不同的是yield不会结束程序
在scrapy
中自带有多线程,也是在settings.py
中设置开启,如下图:
4.存储数据:存储爬取内容
数据的保存放在pipeslines.py
数据管道文件中,首先,这是两个页面,所以需要在ganji.py
中将item
返回出去,用到的语句是:yield item
(这与return
不同的是当每次循环需要返回一个对象的时候,return
无法做到,yield
能不让程序结束)。接着,在pipeslines.py
中,
当然要实现数据的保存,运行爬虫时的命令要改为:scrapy crawl 爬虫名称 -o zf.json
,zj.json就是要保存的文件名及格式,具体代码如下:
import json
class MyspiderPipeline:
def process_item(self, item, spider):
json.dump(dict(item),self.f,ensure_ascii=False)
# 换行
self.f.write('\n')
return item
def open_spider(self,spider):
print('open')
self.f = open('zf11.txt', 'a', encoding='utf-8')
def close_spider(self, spider):
print('close')
self.f.close()
因为在
ganji.py
中的租房对象是一个一个返回的,所以为了不让文件的打开,关闭重复执行,调用了open_spider
和close_spider
这两个内置函数(只要写了就会自动执行,分别是在爬虫运行时,爬虫关闭时执行),f
是自定义的文件对象。文件采用追加'a'
的方式添加数据。ensure_ascii=False
为了让保存的文件中不显示ASC码。