一、爬虫入门
网络爬虫(又被称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的称为网页追逐者),是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本。
运用python3.6中的urllib.request
1.快速爬取一个网页
(1)get请求方式

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# Author:Du Fei
import urllib.request
# keywd = "python"
keywd ="百度"
#解决中文编码问题
keywd=urllib.request.quote(keywd)
url = "http://www.baidu.com/s?wd=" +keywd
req =urllib.request.Request(url)
#urlopen将网页存到内存
data =urllib.request.urlopen(req).read()
fh=open("F:/python/data/douban/2.html","wb")
fh.write(data)
fh.close()
(2)post请求方式

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# Author:Du Fei
#post请求
#登录模拟
import urllib.request
import urllib.parse
url ="http://www.iqianyue.com/mypost/"
#对字段相应设置
mydata=urllib.parse.urlencode({
"name":"ceo@iqiaa.com",
"pass":"123ssd"
}).encode("utf-8")
req =urllib.request.Request(url,mydata)
data =urllib.request.urlopen(req).read()
fh =open("F:/python/data/douban/2_1.html","wb")
fh.write(data)
fh.close()
2.模拟浏览器访问
应用场景:有些网页为了防止别人恶意采集其信息所以进行了一些反爬虫的设置,而我们又想进行爬取。
解决方法:设置一些Headers信息(User-Agent),模拟成浏览器去访问这些网站。
爬取淘宝高清图片

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# Author:Du Fei
import urllib.request
import re
keyname="连衣裙"
#编码
key=urllib.request.quote(keyname)
#User-Agent :Mozilla/5.0 (Windows NT 10.0; …) Gecko/20100101 Firefox/60.0
#伪装成火狐浏览器
headers=("User-Agent","Mozilla /5.0 (Windows NT 10.0; Win64; x6;rv:60.0) Gecko/20100101 Firefox/60.0")
#创建opener对象
opener = urllib.request.build_opener()
#添加报头
opener.addheaders=[headers]
#将opener添加为全局
urllib.request.install_opener(opener)
for i in range(0,1):
#构造网址
url ="https://s.taobao.com/list?spm=a217m.8316598.313651-static.30.3f8533d5oZ7vEf&q="+key+"&cat=50344007&style=grid&seller_type=taobao&bcoffset=12&s=" +str(i*60)
data = urllib.request.urlopen(url).read().decode("utf-8", "ingnore")
#定义正则
pat = 'pic_url":"//(.*?)"'
#图片网址
image_list=re.compile(pat).findall(data)
print(image_list)
for j in range(0,len(image_list)):
thisimg = image_list[j]
thisimg_url ="http://" +thisimg
file="F:/python/data/douban/img/" +str(i)+str(j)+".jpg"
urllib.request.urlretrieve(thisimg_url,filename=file)
爬取CSDN数据

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# Author:Du Fei
import urllib.request
import re
url ="http://blog.csdn.net/"
#伪装成浏览器
#User-Agent用户代理
headers=("User-Agent","Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36")
#创建opener对象
opener = urllib.request.build_opener()
#添加报头
opener.addheaders=[headers]
#将opener添加为全局
urllib.request.install_opener(opener)
#获取url数据
data =urllib.request.urlopen(url).read().decode("utf-8","ingnore")
pat ='<a href="(.*?)" target="_blank" data-track'
result=re.compile(pat).findall(data)
for i in range(0,len(result)):
file = "F:/python/data/douban/csdn/" + str(i) + ".html"
urllib.request.urlretrieve(result[i],filename=file)
print("第"+str(i)+"爬取成功")
3.异常处理
爬虫在爬取网站上的数据常见的错误:URLError和HTTPError
脚本中加入异常处理机制使爬虫脚本更稳健。
爬取新浪新闻首页

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# Author:Du Fei
"""
需求:将新浪新闻首页(http://news.sina.com.cn/)所有新闻都爬取到本地
思路:先爬首页,通过正则获取所有新闻链接,然后依次爬取新闻,并存储到本地
"""
import urllib.request
import urllib.error
import re
#获取首页
#urlopen将网页存到内存
data =urllib.request.urlopen("http://news.sina.com.cn/").read()
#获取的数据编码
data2=data.decode("utf-8","ignore")
pat ='<a href="(http://news.sina.com.cn/.*?)"'
allurl=re.compile(pat).findall(data2)
for i in range(0,len(allurl)):
try:
print("第"+str(i)+"次爬取")
this_url=allurl[i]
file="F:/python/data/douban/sinanews/" +str(i) + ".html"
#网页下载到本地
urllib.request.urlretrieve(this_url,file)
print("---------------成功--------------")
except urllib.error.URLError as e:
if hasattr(e,"code"):
print(e.code)
if hasattr(e,"reason"):
print(e.reason)
4.代理服务器
(1)使用代理服务器的一般格式

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# Author:Du Fei
#西祠免费代理IP
#http://www.xicidaili.com/
#183.167.217.152 63000
import urllib.request
import urllib.error
#代理服务器
def use_proxy(url,proxy_addr):
try:
#代理IP
proxy=urllib.request.ProxyHandler({"http":proxy_addr})
#创建opener对象
opener=urllib.request.build_opener(proxy,urllib.request.HTTPHandler)
#将opener添加为全局
urllib.request.install_opener(opener)
data=urllib.request.urlopen(url).read().decode("utf-8","ignore")
return data
except urllib.error.URLError as e:
if hasattr(e, "code"):
print(e.code)
if hasattr(e, "reason"):
print(e.reason)
proxy_addr="221.228.17.172:8181"
url="http://www.baidu.com"
data =use_proxy(url,proxy_addr)
print(len(data))
(2)微信爬虫
所谓微信爬虫,及自动获取微信的相关文章信息的一种爬虫。微信对我 们的限制是很多的,所以,我们需要采取一些手段解决这些限制,主要 包括伪装浏览器、使用代理IP等方式

#http://weixin.sogou.com/
import re
import urllib.request
import time
import urllib.error
import urllib.request
#自定义函数,功能为使用代理服务器爬一个网址
def use_proxy(proxy_addr,url):
#建立异常处理机制
try:
req=urllib.request.Request(url)
# 添加报头
req.add_header('User-Agent', 'Mozilla /5.0 (Windows NT 10.0; Win64; x6;rv:60.0) Gecko/20100101 Firefox/60.0')
proxy= urllib.request.ProxyHandler({'http':proxy_addr})
# 创建opener对象
opener = urllib.request.build_opener(proxy, urllib.request.HTTPHandler)
# 将opener添加为全局
urllib.request.install_opener(opener)
# 获取req数据
data = urllib.request.urlopen(req).read()
return data
except urllib.error.URLError as e:
if hasattr(e,"code"):
print(e.code)
if hasattr(e,"reason"):
print(e.reason)
#若为URLError异常,延时10秒执行
time.sleep(10)
except Exception as e:
print("exception:"+str(e))
#若为Exception异常,延时1秒执行
time.sleep(1)
#设置关键词
key="Python"
#设置代理服务器,该代理服务器有可能失效,读者需要换成新的有效代理服务器
# proxy="127.0.0.1:8888"
proxy="221.228.17.172:8181"
#爬多少页
for i in range(0,10):
key=urllib.request.quote(key)
thispageurl="http://weixin.sogou.com/weixin?type=2&query="+key+"&page="+str(i)
#a="http://blog.csdn.net"
thispagedata=use_proxy(proxy,thispageurl)
print(len(str(thispagedata)))
pat1='<a href="(.*?)"'
rs1=re.compile(pat1,re.S).findall(str(thispagedata))
if(len(rs1)==0):
print("此次("+str(i)+"页)没成功")
continue
for j in range(0,len(rs1)):
thisurl=rs1[j]
thisurl=thisurl.replace("amp;","")
file="F:/python/data/weixin/第"+str(i)+"页第"+str(j)+"篇文章.html"
thisdata=use_proxy(proxy,thisurl)
try:
fh=open(file,"wb")
fh.write(thisdata)
fh.close()
print("第"+str(i)+"页第"+str(j)+"篇文章成功")
except Exception as e:
print(e)
print("第"+str(i)+"页第"+str(j)+"篇文章失败")
5.多线程爬虫
多线程,即程序中的某些程序段并行执行,合理地设置多线程,可以让爬虫效率更高。
(1)普通爬虫(爬取糗事百科)

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# Author:Du Fei
import urllib.request
import re
import urllib.error
headers=("User-Agent","Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36")
opener = urllib.request.build_opener()
opener.addheaders=[headers]
urllib.request.install_opener(opener)
for i in range(1,2):
try:
#https://www.qiushibaike.com/8hr/page/1/
url="https://www.qiushibaike.com/8hr/page/"+str(i)
pagedata=urllib.request.urlopen(url).read().decode("utf-8","ignore")
#<div class="content"><span></span></div>
pat ='<div class="content">.*?<span>(.*?)</span>.*?</div>'
#可能有多行 re.S
datalist=re.compile(pat,re.S).findall(pagedata)
for j in range(0,len(datalist)):
print("第"+str(i)+"页第"+str(j)+"个段子的内容是:")
print(datalist[j])
except urllib.error.URLError as e:
if hasattr(e, "code"):
print(e.code)
if hasattr(e, "reason"):
print(e.reason)
except Exception as e:
print(e)
print("第" + str(i) + "页第" + str(j) + "篇文章失败")
(2)多线程爬虫(爬取糗事百科)

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# Author:Du Fei
import urllib.request
import re
import urllib.error
import threading
headers=("User-Agent","Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36")
opener = urllib.request.build_opener()
opener.addheaders=[headers]
urllib.request.install_opener(opener)
class One(threading.Thread):
#初始化
def __init__(self):
#初始化线程
threading.Thread.__init__(self)
#线程要做的事情
def run(self):
#奇数页
for i in range(1,36,2):
try:
# https://www.qiushibaike.com/8hr/page/1/
url = "https://www.qiushibaike.com/8hr/page/" + str(i)
pagedata = urllib.request.urlopen(url).read().decode("utf-8", "ignore")
# <div class="content"><span></span></div>
pat = '<div class="content">.*?<span>(.*?)</span>.*?</div>'
# 可能有多行 re.S
datalist = re.compile(pat, re.S).findall(pagedata)
for j in range(0, len(datalist)):
print("第" + str(i) + "页第" + str(j) + "个段子的内容是:")
print(datalist[j])
except urllib.error.URLError as e:
if hasattr(e, "code"):
print(e.code)
if hasattr(e, "reason"):
print(e.reason)
class Two(threading.Thread):
#初始化
def __init__(self):
#初始化线程
threading.Thread.__init__(self)
#线程要做的事情
def run(self):
#偶数页
for i in range(0,36,2):
try:
# https://www.qiushibaike.com/8hr/page/1/
url = "https://www.qiushibaike.com/8hr/page/" + str(i)
pagedata = urllib.request.urlopen(url).read().decode("utf-8", "ignore")
# <div class="content"><span></span></div>
pat = '<div class="content">.*?<span>(.*?)</span>.*?</div>'
# 可能有多行 re.S
datalist = re.compile(pat, re.S).findall(pagedata)
for j in range(0, len(datalist)):
print("第" + str(i) + "页第" + str(j) + "个段子的内容是:")
print(datalist[j])
except urllib.error.URLError as e:
if hasattr(e, "code"):
print(e.code)
if hasattr(e, "reason"):
print(e.reason)
one =One()
one.start()
two=Two()
two.start()
二、Scrapy框架
实战
1.自动模拟登陆豆瓣
(1).douban.py

# -*- coding: utf-8 -*-
import scrapy
from scrapy.http import Request,FormRequest
import urllib.request
class DbSpider(scrapy.Spider):
name = "db"
allowed_domains = ["douban.com"]
header={"User-Agent:":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.22 Safari/537.36 SE 2.X MetaSr 1.0"}
'''
start_urls = (
'http://www.douban.com/',
)
'''
def start_requests(self):
return [Request("https://accounts.douban.com/login",callback=self.parse,meta={"cookiejar":1})]
def parse(self, response):
captcha=response.xpath("//img[@id='captcha_image']/@src").extract()
url="https://accounts.douban.com/login"
if len(captcha)>0:
print("此时验证码")
#半自动化验证码
#验证码下载到本地地址
localpath="F:/python/data/db/captcha.png"
urllib.request.urlretrieve(captcha[0],filename=localpath)
print("请查看本地验证码图片并输入验证码")
captcha_value=input()
data={
"form_email": "aaa@163.com",
"form_password": "abded",
"captcha-solution":captcha_value,
"redir":"https://www.douban.com/people/233455/",
}
else:
print("此时没有验证码")
data={
"form_email": "aaa@163.com",
"form_password": "abded",
"redir":"https://www.douban.com/people/233455/",
}
print("登陆中……")
return [FormRequest.from_response(response,
meta={"cookiejar": response.meta["cookiejar"]},
headers=self.header,
formdata=data,
callback=self.next,
)]
def next(self, response):
print("此时已经登陆完成并爬取了个人中心的数据")
title = response.xpath("/html/head/title/text()").extract()
# note = response.xpath("//div[@class='note']/text()").extract()
print(title[0])
# print(note[0])
(2).setting.py

USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36'
2.爬取当当网数据入Linux中的mysql
(1)items.py

import scrapy
class DangdangItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
title= scrapy.Field()
link =scrapy.Field()
comment=scrapy.Field()
(2)dd.py

# -*- coding: utf-8 -*-
import scrapy
from dangdang.items import DangdangItem
from scrapy.http import Request
class DdSpider(scrapy.Spider):
name = 'dd'
allowed_domains = ['dangdang.com']
start_urls = ['http://dangdang.com/']
def parse(self, response):
item=DangdangItem()
item["title"]=response.xpath("//a[@name='itemlist-picture']/@title").extract()
item["link"]=response.xpath("//a[@name='itemlist-picture']/@href").extract()
item["comment"]=response.xpath("//a[@class='search_comment_num']/text()").extract()
yield item
for i in range(2,5):
url="http://category.dangdang.com/pg"+str(i)+"-cp01.54.00.00.00.00.html"
yield Request(url,callback=self.parse)
(3)pipelines.py

# -*- coding: utf-8 -*-
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html
import pymysql
class DangdangPipeline(object):
def process_item(self, item, spider):
#连接数据库
conn = pymysql.connect(host='XXXX', port=3306, user='root', passwd='XXX', db='XX',
charset='utf8')
print(conn)
# 创建操作的游标
cursor = conn.cursor()
# 设置字符编码及自动提交
cursor.execute('set names utf8') # 固定格式
for i in range(0,len(item["title"])):
title=item["title"][i]
link=item["link"][i]
comment=item["comment"][i]
# print(title)
# print(link)
# print(comment)
sql = "insert into boods(title,link,comment) values(%s,%s,%s)"
cursor.execute(sql, (title, link, comment))
cursor.close()
conn.close()
return item
(4)setting.py中添加

ROBOTSTXT_OBEY = False
ITEM_PIPELINES = {
'dangdang.pipelines.DangdangPipeline': 300,
}
3.爬取京东商城商品信息(自动爬取)
创建一个crawl爬虫,爬取京东的商品信息,并且写入数据库中。
(1)创建scrapy项目
scrapy startproject jingdong
(2)常见自动爬取文件
scrapy genspider -t crawl jd jd.com
(3)items.py

# -*- coding: utf-8 -*-
# Define here the models for your scraped items
#
# See documentation in:
# https://doc.scrapy.org/en/latest/topics/items.html
import scrapy
class JingdongItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
#商品id
id = scrapy.Field()
#商品名
title = scrapy.Field()
#商品所在商店名
shop = scrapy.Field()
# 商品所在商店链接
shoplink = scrapy.Field()
#商品价格
price = scrapy.Field()
#商品好评
comment = scrapy.Field()
(4)jd.py

# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from jingdong.items import JingdongItem
import re
import urllib.request
#自动爬虫
class JdSpider(CrawlSpider):
name = 'jd'
allowed_domains = ['jd.com']
start_urls = ['http://jd.com/']
rules = (
Rule(LinkExtractor(allow=''), callback='parse_item', follow=True),
)
def parse_item(self, response):
try:
#实例化容器
i = JingdongItem()
#获取当前页
thisurl = response.url
pat ="item.jd.com/(.*?).html"
#在thisurl中查找有没有pat这样格式的表达式
x=re.search(pat,thisurl)
if(x):
#获取商品的id
thisid=re.compile(pat).findall(thisurl)[0]
#标题
title=response.xpath("//div[@id='spec-n1']/img[@id='spec-img']/@alt").extract()
#商家
shop=response.xpath("//div[@class='name']/a/text()").extract()
#商家连接
shoplink=response.xpath("//div[@class='name']/a/@href").extract()
#价格url
priceurl ="https://c0.3.cn/stock?skuId="+str(thisid)+"&area=1_72_2799_0&venderId=1000000904&cat=9987,653,655&buyNum=1&choseSuitSkuIds=&extraParam={%22originid%22:%221%22}&ch=1&fqsp=0&pduid=248862164&pdpin=&detailedAdd=null&callback=jQuery6703843"
#好评url
commenturl ="https://club.jd.com/comment/productCommentSummaries.action?referenceIds="+str(thisid)+"&callback=jQuery1884180&_=1533995028715"
#将价格网页存到内存
pricedata=urllib.request.urlopen(priceurl).read().decode("utf-8","ignore")
# 将好评网页存到内存
commentdata=urllib.request.urlopen(commenturl).read().decode("utf-8","ignore")
pricepat='"p":"(.*?)"'
commentpat='"GoodRateShow":(.*?),'
price=re.compile(pricepat).findall(pricedata)
comment = re.compile(commentpat).findall(commentdata)
#爬取每个字段都有值得商品信息
if(len(title) and len(shop) and len(shoplink) and len(price) and len(comment)):
i["id"] =thisid
i["title"] =title
i["shop"] =shop
i["shoplink"]=shoplink
i["price"]=price
i["comment"]=comment
else:
pass
else:
pass
return i
except Exception as e:
print(e)
(5)pipelines.py

# -*- coding: utf-8 -*-
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html
import pymysql
# class JingdongPipeline(object):
# def process_item(self, item, spider):
# print(item["id"])
# print(item["title"][0])
# print(item["shop"][0])
# print(item["shoplink"][0])
# print(item["price"][0])
# print(item["comment"][0])
# print("-----------------")
# return item
class JingdongPipeline(object):
def process_item(self, item, spider):
#连接数据库
conn = pymysql.connect(host='xxxxx', port=3306, user='root', passwd='xxxx', db='xxxx',
charset='utf8')
print(conn)
# 创建操作的游标
cursor = conn.cursor()
# 设置字符编码及自动提交
cursor.execute('set names utf8') # 固定格式
id=item["id"]
title=item["title"][0]
shop=item["shop"][0]
shoplink=item["shoplink"][0]
price=item["price"][0]
comment=item["comment"][0]
sql = "insert into jd(id,title,shop,shoplink,price,comment) values(%s,%s,%s,%s,%s,%s)"
cursor.execute(sql,(id,title,shop,shoplink,price,comment))
cursor.close()
conn.close()
return item
(6)settings.py

# -*- coding: utf-8 -*-
# Scrapy settings for jingdong project
#
# For simplicity, this file contains only settings considered important or
# commonly used. You can find more settings consulting the documentation:
#
# https://doc.scrapy.org/en/latest/topics/settings.html
# https://doc.scrapy.org/en/latest/topics/downloader-middleware.html
# https://doc.scrapy.org/en/latest/topics/spider-middleware.html
BOT_NAME = 'jingdong'
SPIDER_MODULES = ['jingdong.spiders']
NEWSPIDER_MODULE = 'jingdong.spiders'
# Crawl responsibly by identifying yourself (and your website) on the user-agent
#USER_AGENT = 'jingdong (+http://www.yourdomain.com)'
# Obey robots.txt rules
ROBOTSTXT_OBEY = True
# Configure maximum concurrent requests performed by Scrapy (default: 16)
#CONCURRENT_REQUESTS = 32
# Configure a delay for requests for the same website (default: 0)
# See https://doc.scrapy.org/en/latest/topics/settings.html#download-delay
# See also autothrottle settings and docs
#DOWNLOAD_DELAY = 3
# The download delay setting will honor only one of:
#CONCURRENT_REQUESTS_PER_DOMAIN = 16
#CONCURRENT_REQUESTS_PER_IP = 16
# Disable cookies (enabled by default)
#COOKIES_ENABLED = False
# Disable Telnet Console (enabled by default)
#TELNETCONSOLE_ENABLED = 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',
#}
# Enable or disable spider middlewares
# See https://doc.scrapy.org/en/latest/topics/spider-middleware.html
#SPIDER_MIDDLEWARES = {
# 'jingdong.middlewares.JingdongSpiderMiddleware': 543,
#}
# Enable or disable downloader middlewares
# See https://doc.scrapy.org/en/latest/topics/downloader-middleware.html
#DOWNLOADER_MIDDLEWARES = {
# 'jingdong.middlewares.JingdongDownloaderMiddleware': 543,
#}
# Enable or disable extensions
# See https://doc.scrapy.org/en/latest/topics/extensions.html
#EXTENSIONS = {
# 'scrapy.extensions.telnet.TelnetConsole': None,
#}
# Configure item pipelines
# See https://doc.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
'jingdong.pipelines.JingdongPipeline': 300,
}
# Enable and configure the AutoThrottle extension (disabled by default)
# See https://doc.scrapy.org/en/latest/topics/autothrottle.html
#AUTOTHROTTLE_ENABLED = True
# The initial download delay
#AUTOTHROTTLE_START_DELAY = 5
# The maximum download delay to be set in case of high latencies
#AUTOTHROTTLE_MAX_DELAY = 60
# The average number of requests Scrapy should be sending in parallel to
# each remote server
#AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
# Enable showing throttling stats for every response received:
#AUTOTHROTTLE_DEBUG = False
# Enable and configure HTTP caching (disabled by default)
# See https://doc.scrapy.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings
#HTTPCACHE_ENABLED = True
#HTTPCACHE_EXPIRATION_SECS = 0
#HTTPCACHE_DIR = 'httpcache'
#HTTPCACHE_IGNORE_HTTP_CODES = []
#HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'
自主学习Python数据分析与数据挖掘中爬虫入门总结。
参考网上教程:https://www.hellobi.com/
