Scrapy框架2
一、进程、线程、协成
1、进程、线程、协成之间的关系
1、 线程是计算机中最小的工作单元。
2、 进程是提供资源供n个线程使用,即进程是最小的管理单元。
3、协程是人为控制的线程。
4、总结:1、python中由于有 GIL锁的存在,所以一个进程中同一时刻只有一个线程被CPU调度,所以在计算密集型,使用多进程而在io密集型使用多线程。
2、使用协成可以实现单线程下的并发,线程中cpu在遇到io操作时就会切到下一个线程中,这样大大的影响的效率于是人为的控制cpu在线程中的切换,当cpu在线程中遇到io会切换到该线程的下一个程序中这就是协成
2、异步非阻塞模块
1、gevent模块,基于协程的异步非阻塞模块。
2、Twisted,基于事件驱动(while循环检测)异步非阻塞模块(框架)。
3、非阻塞是指不等待 ,异步是指回调,执行某个任务完成之后,自动执行的函数。回调函数
二、scarpy框架
1、重写scrapy请求头方法
# -*- coding: utf-8 -*-
import scrapy
from scrapy.http import Request
from scrapy.selector import HtmlXPathSelector #response= HtmlXPathSelector(response)主要是在parse函数中使用,
from urllib.parse import urlencode
from ..items import MyscrapyItem
class BaiduSpider(scrapy.Spider):
name = 'baidu' #爬虫应用的名称,通过此名称启动爬虫命令
allowed_domains = ['www.amazon.cn'] # 允许爬取数据的域名
def __init__(self,keywords=None,*args,**kwargs):
super(BaiduSpider,self).__init__(*args,**kwargs)
self.keywords=keywords #主要是接受一些外部传进来的参数
def start_requests(self): #重写scrapy请求头url必须写这个名字的函数
url='https://www.amazon.cn/s/ref=nb_sb_noss_1?' #新url
parmas={
'field-keywords':self.keywords #主要是定义一些外部传进来的参数
}
url=url+urlencode(parmas,encoding='utf-8') #路径拼接,形成一个完整的url路径
yield Request(url,callback=self.parse_index) #yield返回数据给回调函数,也可以是return+[]的方式返回数据给回调函数,yield返回的是生成器,return+[]返回的可迭代对象
# return [Request(url='www.baidu.com',callback=self.parse),] def parse_index(self, response):
urls=response.xpath('//*[contains(@id,"result_"]/div/div[3]/div[1]/a/@href').extract()
for url in urls:
print(url)
yield Request(url,callback=self.parse) #返回详情页的信息
next_url=response.urljoin(response.xpath('//*[@id="pagnNextLink"]/@href').extract_first())
yield Request(next_url,callback=self.parse_index) #返回下一页的url路径,返回的是个Request对象
def parse(self, response):
price=response.xpath('//*[@id="priceblock_ourprice"]/text()').extract_first().strip()
colour=response.xpath('//*[@id="variation_color_name"]/div/span/text()').extract_first().strip()
item=MyscrapyItem()
item['price']=price
item['colour']=colour
return item #返回的是个item对象
或者是这种格式,只要返回的是个item对象及格
obj = XiaoHuarItem(price=price, colour=colour, )
yield obj
2、items文件写法
import scrapy
class MyscrapyItem(scrapy.Item):
price=scrapy.Field()
colour=scrapy.Field()
3、自定义pipelines文件
from scrapy.exceptions import DropItem #主动触发异常用的
class CustomPipeline(object):
def __init__(self,val):
self.vale = val #构造一个对象特有的方法
def process_item(self, item, spider): #用于存储爬取的数据,可以存储在文件也可以存储在数据库。 f=opne('item.log','a+')
f.write(item.price,item.colour)
f.close()
return item # return表示会被后续的pipeline继续处理,因为有可以一个爬虫项目会有多个爬虫程序一起执行,但是都有写入同一个文件中,那就让第一个文件写入,其余后面的文件不用重复写入。 # raise DropItem() # 表示将item丢弃,不会被后续pipeline处理
# if spider.name='baidu': #这就表示该文件只是存储这个名字的爬虫爬取的数据
#f=opne('item.log','a+')
#f.write(item.price,item.colour)
#f.close()
#return item
@classmethod
def from_crawler(cls, crawler):#初始化时候,用于创建pipeline对象,crawler这个类可以得到settings配置文件内的数据,如果需要给pipeline对象创建某个额外的参数就可以用这中方法
val = crawler.settings.getint('MMMM') #val = crawler.settings.get('MMMM') 读取配置文件的某个信息
return cls(val) def open_spider(self,spider): #在爬虫程序开始执行之前调用该方法
print('000000') def close_spider(self,spider): #在爬虫程序关闭后调用该方法
print('111111') 总结:这五个方法的执行顺序: 先执行from_crawler方法,用于初始化数据的时候读取配置文件,然后再执行爬虫程序的__init__方法,然后程序启动前执行open_spider方法,程序结束前保存数据时执行process_item方法,程序结果后
执行close_spider方法。
4、自定义url去重功能
scrapy默认使用 scrapy.dupefilter.RFPDupeFilter 进行去重,settings相关配置有:
DUPEFILTER_CLASS = 'scrapy.dupefilter.RFPDupeFilter' #去重文件,可以自定义
DUPEFILTER_DEBUG = False #默认去重功能是关闭的可以在settings中打开去重功能也可以在爬虫程序中手动指定去重 yield Request(url,callback=self.parse,dont_filter=True)
JOBDIR = "保存范文记录的日志路径,如:/root/" # 最终路径为 /root/requests.seen 默认url是存放在内存中的,只要添加上jobdir就可以吧url存放在指定文件中
自己写一个去重规则
class RepeatUrl:
def __init__(self):
self.visited_url = set() #默认是一个集合,也可以是自定义链接一个数据库,文件等 @classmethod
def from_settings(cls, settings):
"""
初始化时,调用
"""
return cls() def request_seen(self, request):
"""
检测当前请求是否已经被访问过
:param request:
:return: True表示已经访问过;False表示未访问过
"""
if request.url in self.visited_url:
return True
self.visited_url.add(request.url)
return False def open(self):
"""
开始爬去请求时,调用
:return:
"""
print('open replication') def close(self, reason):
"""
结束爬虫爬取时,调用
:param reason:
:return:
"""
print('close replication') def log(self, request, spider):
"""
记录日志
:param request:
:param spider:
:return:
"""
print('repeat', request.url)
注意:写好自定义的去重规则后要将该文件设置到settings中该规程才能生效 比如:DUPEFILTER_CLASS = 'scrapy.dupefilter.RFPDupeFilter'
5、自定义命令
#自定制一键启动所有爬虫命令
from scrapy.commands import ScrapyCommand
from scrapy.utils.project import get_project_settings class Command(ScrapyCommand):
requires_project = True def syntax(self):
return '[options]' def short_desc(self):
return 'Runs all of the spiders' def run(self, args, opts): #这是源码的入口
spider_list = self.crawler_process.spiders.list()
for name in spider_list: #循环所有的爬虫程序名称
self.crawler_process.crawl(name, **opts.__dict__) #准备所有的爬虫程序
self.crawler_process.start() #开始爬取任务
注意:1、在spiders同级创建任意目录,如:commands
2、在其中创建 crawlall.py 文件 (此处文件名就是自定义的命令)
3、在settings.py 中添加配置 COMMANDS_MODULE = '项目名称.目录名称'
4、在项目目录执行命令:scrapy crawlall
#自定制在py文件中启动某个爬虫命令
from scrapy.cmdline import execute
execute(['scrapy', '命令', '爬虫程序名称','-a','传入的参数'])
eg:execute(['scrapy', 'crawl', 'baidu','-a','keywords=iphone7'])
注意:自定制的py文件名称必须是entrypoint.py
6、自定义信号
from scrapy import signals
class MyExtension(object):
def __init__(self, value):
self.value = value #定义自己想要的方法 @classmethod
def from_crawler(cls, crawler): #扩展功能,在扩展功能里注册信号
val = crawler.settings.getint('MMMM')#读取配置文件信息
ext = cls(val)
crawler.signals.connect(ext.spider_opened, signal=signals.spider_opened) #注册信号
crawler.signals.connect(ext.spider_closed, signal=signals.spider_closed) #注册信号,可以注册很多信号,看源码
return ext #如果不需要扩展功能那么就直接注册信号即可 def spider_opened(self, spider): #爬虫程序启动时触发的信号
print('open') def spider_closed(self, spider): #爬虫程序关闭时触发的信号
print('close')
注释:1、在项目目录下创建一个py文件,将自定义的信号写入该文件,建议py文件名为extensions
2、在settings中注册自定义信号的文件
eg:EXTENSIONS = {'scrapy.extensions.telnet.TelnetConsole': 300,}
7、中间件
#下载中间件
class DownMiddleware1(object):
def process_request(self, request, spider): #请求过去的中间件带的时request
"""
请求需要被下载时,经过所有下载器中间件的process_request调用
return:
None,继续后续中间件去下载,默认就是什么都不返回;
Response对象,停止process_request的执行,开始执行process_response,先导入Response(from scrapy.http import Response)
eg:return Response(url='http://www.baidu.com',request=request) #只要返回Response对象就不会执行后续的Request,而是直接执行Response
Request对象,停止中间件的执行,将Request重新调度器 #如果返回的是Request对象那就不会执行后续的Request和Reques而是直接到引擎,重新调度
raise IgnoreRequest异常,停止process_request的执行,开始执行process_exception
"""
pass def process_response(self, request, response, spider): #返回数据的中间件带的时response
"""
spider处理完成,返回时调用,默认返回的就是Response
return:
Response 对象:转交给其他中间件process_response
Request 对象:停止中间件,request会被重新调度下载
raise IgnoreRequest 异常:调用Request.errback
"""
return response def process_exception(self, request, exception, spider):
"""
当下载处理器(download handler)或 process_request() (下载中间件)抛出异常的时候调用
:return:
None:继续交给后续中间件处理异常;
Response对象:停止后续process_exception方法,正确执行后续方法
Request对象:停止中间件后续方法,request将会被重新调用下载
"""
return None
注释:1、在项目目录下创建.py文件,将中间件代码写在该文件中,建议文件名称为DownMiddleware
2、在settings中注册中间件,DOWNLOADER_MIDDLEWARES = {'chouti.middlewares.MyCustomDownloaderMiddleware': 600,},写了几个中间件就注册几个中间件
3、可以在中间件的process_request中将所有的Request请求添加上请求头,添加cookies等操作,也可以添加代理,判断那个Request需要代理。
#爬虫中间件
class SpiderMiddleware(object):
def process_spider_input(self,response, spider):
"""下载完成,执行,然后交给parse处理,默认返回none"""
pass
def process_spider_output(self,response, result, spider):
"""spider处理完成,返回时调用,return: 必须返回包含 Request 或 Item 对象的可迭代对象(iterable)"""
return result
def process_spider_exception(self,response, exception, spider):
"""异常调用,return: None,继续交给后续中间件处理异常;含 Response 或 Item 的可迭代对象(iterable),交给调度器或pipeline"""
return None
def process_start_requests(self,start_requests, spider):
"""爬虫启动时调用,return: 包含 Request 对象的可迭代对象,由于爬虫只是启动一次所以process_start_requests也只是调用一次 """
return start_requests
注释:1、在项目目录下创建.py文件,将中间件代码写在该文件中,建议文件名称为SpiderMiddleware
2、在settings中注册中间件,SPIDER_MIDDLEWARES = { 'chouti.middlewares.ChoutiSpiderMiddleware': 600,},写了几个中间件就注册几个中间件
8、自定义代理
代理,需要在环境变量中设置
from scrapy.contrib.downloadermiddleware.httpproxy import HttpProxyMiddleware 方式一:使用默认
import os
os.environ
{
http_proxy:http://root:woshiniba@192.168.11.11:9999/ #有的代理链接是需要用户名和密码的,这个就是root为用户名,woshiniba为密码
https_proxy:http://192.168.11.11:9999/
} #该方法只需要在爬虫程序文件的类的__init__中添加上就可以了,程序会判断只要是_prxoy结尾的就会吧这个代理拿出来
方式二:使用自定义下载中间件
def to_bytes(text, encoding=None, errors='strict'):
if isinstance(text, bytes):
return text
if not isinstance(text, six.string_types):
raise TypeError('to_bytes must receive a unicode, str or bytes '
'object, got %s' % type(text).__name__)
if encoding is None:
encoding = 'utf-8'
return text.encode(encoding, errors) class ProxyMiddleware(object):#代理中间件
def process_request(self, request, spider):
PROXIES = [
{'ip_port': '111.11.228.75:80', 'user_pass': ''},
{'ip_port': '120.198.243.22:80', 'user_pass': ''},
{'ip_port': '111.8.60.9:8123', 'user_pass': ''},
{'ip_port': '101.71.27.120:80', 'user_pass': ''},
{'ip_port': '122.96.59.104:80', 'user_pass': ''},
{'ip_port': '122.224.249.122:8088', 'user_pass': ''},
]
proxy = random.choice(PROXIES)#随机选择一个代理,高明就高明在这个地方,默认代理是不能随机生成的,所有也会有被封的风险。
if proxy['user_pass'] is not None:
request.meta['proxy'] = to_bytes("http://%s" % proxy['ip_port'])#添加上代理
encoded_user_pass = base64.encodestring(to_bytes(proxy['user_pass']))#代理加密,固定加密方法
request.headers['Proxy-Authorization'] = to_bytes('Basic ' + encoded_user_pass) #代理授权
#print "**************ProxyMiddleware have pass************" + proxy['ip_port']
else:
#print "**************ProxyMiddleware no pass************" + proxy['ip_port']
request.meta['proxy'] = to_bytes("http://%s" % proxy['ip_port'])#添加代理 注释:1、在爬虫项目路径中创建代理文件
2、在settings中注册上代理 DOWNLOADER_MIDDLEWARES = { 'step8_king.middlewares.ProxyMiddleware': 500,}
9、settings配置文件详解
1. 爬虫名称
BOT_NAME = 'step8_king' 2. 爬虫应用路径
SPIDER_MODULES = ['step8_king.spiders'] #其实就是创建的爬虫程序名称+spiders
NEWSPIDER_MODULE = 'step8_king.spiders'
3. 客户端 user-agent请求头
# USER_AGENT = 'step8_king (+http://www.yourdomain.com)' 4. 禁止爬虫配置(机器人协议)
# ROBOTSTXT_OBEY = True
5. 并发请求数
# CONCURRENT_REQUESTS = 4 #一个线程最大能并发多少个请求,力度粗 6. 延迟下载秒数
# DOWNLOAD_DELAY = 2 默认是秒,每多少秒一次下载 7. 单域名访问并发数,并且延迟下次秒数也应用在每个域名 力度细
# CONCURRENT_REQUESTS_PER_DOMAIN = 2
# 单IP访问并发数,如果有值则忽略:CONCURRENT_REQUESTS_PER_DOMAIN,并且延迟下次秒数也应用在每个IP 力度细
# CONCURRENT_REQUESTS_PER_IP = 3 8. 是否支持cookie,cookiejar进行操作cookie
# COOKIES_ENABLED = True
# COOKIES_DEBUG = True
9. Telnet用于查看当前爬虫的信息,操作爬虫等...
# 使用telnet ip port ,然后通过命令操作
# TELNETCONSOLE_ENABLED = True
# TELNETCONSOLE_HOST = '127.0.0.1'
# TELNETCONSOLE_PORT = [6023,] 10. 默认请求头,死板
# 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',
# } 11. 定义pipeline处理请求
# ITEM_PIPELINES = {
# 'step8_king.pipelines.JsonPipeline': 700,
# 'step8_king.pipelines.FilePipeline': 500,
# } 12. 自定义扩展,基于信号进行调用
# Enable or disable extensions
# See http://scrapy.readthedocs.org/en/latest/topics/extensions.html
# EXTENSIONS = {
# # 'step8_king.extensions.MyExtension': 500,
# } 13. 爬虫允许的最大深度,可以通过meta查看当前深度;0表示无深度
# DEPTH_LIMIT = 3 14. 爬取时,0表示深度优先Lifo(默认);1表示广度优先FiFo # 后进先出,深度优先
# DEPTH_PRIORITY = 0
# SCHEDULER_DISK_QUEUE = 'scrapy.squeue.PickleLifoDiskQueue'
# SCHEDULER_MEMORY_QUEUE = 'scrapy.squeue.LifoMemoryQueue'
# 先进先出,广度优先
# DEPTH_PRIORITY = 1
# SCHEDULER_DISK_QUEUE = 'scrapy.squeue.PickleFifoDiskQueue'
# SCHEDULER_MEMORY_QUEUE = 'scrapy.squeue.FifoMemoryQueue' 15. 调度器队列
# SCHEDULER = 'scrapy.core.scheduler.Scheduler'
# from scrapy.core.scheduler import Scheduler 16. 访问URL去重
# DUPEFILTER_CLASS = 'step8_king.duplication.RepeatUrl'
"""
17. 自动限速算法
from scrapy.contrib.throttle import AutoThrottle
自动限速设置
1. 获取最小延迟 DOWNLOAD_DELAY
2. 获取最大延迟 AUTOTHROTTLE_MAX_DELAY
3. 设置初始下载延迟 AUTOTHROTTLE_START_DELAY
4. 当请求下载完成后,获取其"连接"时间 latency,即:请求连接到接受到响应头之间的时间
5. 用于计算的... AUTOTHROTTLE_TARGET_CONCURRENCY
target_delay = latency / self.target_concurrency
new_delay = (slot.delay + target_delay) / 2.0 # 表示上一次的延迟时间
new_delay = max(target_delay, new_delay)
new_delay = min(max(self.mindelay, new_delay), self.maxdelay)
slot.delay = new_delay
"""
10、https证书设置
Https访问时有两种情况:
1. 要爬取网站使用的可信任证书(默认支持)
DOWNLOADER_HTTPCLIENTFACTORY = "scrapy.core.downloader.webclient.ScrapyHTTPClientFactory"
DOWNLOADER_CLIENTCONTEXTFACTORY = "scrapy.core.downloader.contextfactory.ScrapyClientContextFactory" 2. 要爬取网站使用的自定义证书
DOWNLOADER_HTTPCLIENTFACTORY = "scrapy.core.downloader.webclient.ScrapyHTTPClientFactory"
DOWNLOADER_CLIENTCONTEXTFACTORY = "step8_king.https.MySSLFactory" # https.py
from scrapy.core.downloader.contextfactory import ScrapyClientContextFactory
from twisted.internet.ssl import (optionsForClientTLS, CertificateOptions, PrivateCertificate) class MySSLFactory(ScrapyClientContextFactory):
def getCertificateOptions(self):
from OpenSSL import crypto
v1 = crypto.load_privatekey(crypto.FILETYPE_PEM, open('/Users/wupeiqi/client.key.unsecure', mode='r').read()) #打开本地保存的证书文件
v2 = crypto.load_certificate(crypto.FILETYPE_PEM, open('/Users/wupeiqi/client.pem', mode='r').read())
return CertificateOptions(
privateKey=v1, # pKey对象
certificate=v2, # X509对象
verify=False,
method=getattr(self, 'method', getattr(self, '_ssl_method', None))
)
其他:
相关类
scrapy.core.downloader.handlers.http.HttpDownloadHandler
scrapy.core.downloader.webclient.ScrapyHTTPClientFactory
scrapy.core.downloader.contextfactory.ScrapyClientContextFactory
相关配置
DOWNLOADER_HTTPCLIENTFACTORY
DOWNLOADER_CLIENTCONTEXTFACTORY
Scrapy框架2的更多相关文章
- Python爬虫Scrapy框架入门(2)
本文是跟着大神博客,尝试从网站上爬一堆东西,一堆你懂得的东西 附上原创链接: http://www.cnblogs.com/qiyeboy/p/5428240.html 基本思路是,查看网页元素,填写 ...
- Python爬虫Scrapy框架入门(1)
也许是很少接触python的原因,我觉得是Scrapy框架和以往Java框架很不一样:它真的是个框架. 从表层来看,与Java框架引入jar包.配置xml或.property文件不同,Scrapy的模 ...
- Scrapy框架使用—quotesbot 项目(学习记录一)
一.Scrapy框架的安装及相关理论知识的学习可以参考:http://www.yiibai.com/scrapy/scrapy_environment.html 二.重点记录我学习使用scrapy框架 ...
- Python爬虫从入门到放弃(十一)之 Scrapy框架整体的一个了解
这里是通过爬取伯乐在线的全部文章为例子,让自己先对scrapy进行一个整理的理解 该例子中的详细代码会放到我的github地址:https://github.com/pythonsite/spider ...
- Python爬虫从入门到放弃(十二)之 Scrapy框架的架构和原理
这一篇文章主要是为了对scrapy框架的工作流程以及各个组件功能的介绍 Scrapy目前已经可以很好的在python3上运行Scrapy使用了Twisted作为框架,Twisted有些特殊的地方是它是 ...
- python爬虫scrapy框架——人工识别登录知乎倒立文字验证码和数字英文验证码(2)
操作环境:python3 在上一文中python爬虫scrapy框架--人工识别知乎登录知乎倒立文字验证码和数字英文验证码(1)我们已经介绍了用Requests库来登录知乎,本文如果看不懂可以先看之前 ...
- 一个scrapy框架的爬虫(爬取京东图书)
我们的这个爬虫设计来爬取京东图书(jd.com). scrapy框架相信大家比较了解了.里面有很多复杂的机制,超出本文的范围. 1.爬虫spider tips: 1.xpath的语法比较坑,但是你可以 ...
- 安装scrapy框架的常见问题及其解决方法
下面小编讲一下自己在windows10安装及配置Scrapy中遇到的一些坑及其解决的方法,现在总结如下,希望对大家有所帮助. 常见问题一:pip版本需要升级 如果你的pip版本比较老,可能在安装的过程 ...
- 关于使用scrapy框架编写爬虫以及Ajax动态加载问题、反爬问题解决方案
Python爬虫总结 总的来说,Python爬虫所做的事情分为两个部分,1:将网页的内容全部抓取下来,2:对抓取到的内容和进行解析,得到我们需要的信息. 目前公认比较好用的爬虫框架为Scrapy,而且 ...
- 利用scrapy框架进行爬虫
今天一个网友问爬虫知识,自己把许多小细节都忘了,很惭愧,所以这里写一下大概的步骤,主要是自己巩固一下知识,顺便复习一下.(scrapy框架有一个好处,就是可以爬取https的内容) [爬取的是杨子晚报 ...
随机推荐
- 在Linux命令行下发送html格式的邮件
在Linux利用formail+sendmail来发送带图片的邮件 formail接收html格式的文件作为邮件的内容,这样就可以解决发送带图片邮件的问题了,因为html中可以插入图片,只要给出的im ...
- 5 月 35 日临近,Google 无法访问,可以使用 Google IP 来解决。
每年都会有几天那啥,你懂的. 直接使用 Google 的域名访问经常会打不开,而使用 Google 的 IP 就会很顺畅. 使用 Chrome 浏览器我们经常都会在地址栏直接搜索,所以我们只要添加一个 ...
- JPA(Java Persistence API)Java持久化API-介绍
JPA全称: Java Persistence API JPA的宗旨是为POJO提供持久化标准规范,能够脱离容器独立运行,很方便开发和测试.JPA通过JDK 5.0注解或XML描述对象-关系表的映射关 ...
- diamond源码阅读-diamond-server
diamond-server 1 增加一条数据 /diamond-server/admin.do?method=postConfig 1.1 调用 this.configService.addConf ...
- 回调函数(callback)是什么?
你到一个商店买东西,刚好你要的东西没有货,于是你在店员那里留下了你的电话,过了几天店里有货了,店员就打了你的电话,然后你接到电话后就到店里去取了货.在这个例子里,你的电话号码就叫回调函数,你把电话留给 ...
- Android Studio Error -- Could not create the Java Virtual Machine
:app:dexDebug Error: Could not create the Java Virtual Machine. Error: A fatal exception has occurre ...
- 小知识,大智慧(restframework 拾忆)
一.直接对query_set序列化,在页面展示的效果是Unicode 编码格式 ,可在json 序列化时候加入一个参数 course_query = DegreeCourse.objects.all( ...
- PHP中foreach详细解读
oreach 语法结构提供了遍历数组的简单方式.foreach 仅能够应用于数组和对象,如果尝试应用于其他数据类型的变量,或者未初始化的变量将发出错误信息.有两种语法: foreach (array_ ...
- LeetCode Problem 169: Majority Element查找多数元素
描述:Given an array of size n, find the majority element. The majority element is the element that app ...
- VLC 媒体播放器
VLC 媒体播放器 VLC 媒体播放器是一个便携式. 免费.开源. 跨平台的媒体播放器. VideoLAN 项目的流式媒体服务器.分为Windows Phone版本和Android版本. 下载地址: ...