码迷,mamicode.com
首页 > 其他好文 > 详细

数据提取

时间:2019-11-22 12:05:45      阅读:74      评论:0      收藏:0      [点我收藏+]

标签:编码   header   创建   安全   mac   obj   数据处理   声明   主线程   

非结构化数据处理(文本)
正则
match 方法:从起始位置开始查找,一次匹配
# match 方法用于查找字符串的头部(也可以指定起始位置),它是一次匹配,只要找到了一个匹配的结果就返回,而不是查找所有匹配的结果。它的一般使用形式如下:
# 其中,string 是待匹配的字符串,pos 和 endpos 是可选参数,指定字符串的起始和终点位置,默认值分别是 0 和 len (字符串长度)。
# 不指定 pos 和 endpos 时,match 方法默认匹配字符串的头部。
#当匹配成功时,返回一个 Match 对象,如果没有匹配上,则返回 None。
import re
pattern = re.compile(r‘\d+‘) # 用于匹配至少一个数字
m = pattern.match(‘one12twothree34four‘) # 查找头部,没有匹配
print (m)
m = pattern.match(‘one12twothree34four‘, 2, 10) # 从‘e‘的位置开始匹配,没有匹配
m = pattern.match(‘one12twothree34four‘, 3, 10) # 从‘1‘的位置开始匹配,正好匹配

group()分组匹配, 提取数据
pattern = re.compile(r‘([a-z]+) ([a-z]+)‘, re.I) # re.I 表示忽略大小写
m = pattern.match(‘Hello World Wide Web‘)
print (m) # 匹配成功,返回一个 Match 对象
#<_sre.SRE_Match object at 0x10bea83e8>

m.group(0) # 返回匹配成功的整个子串
#‘Hello World‘
m.group(1) # 返回第一个分组匹配成功的子串
#‘Hello‘
m.group(2) # 返回第二个分组匹配成功的子串
#‘World‘
m.span(0) # 返回匹配成功的整个子串的索引
#(0, 11)
m.span(1) # 返回第一个分组匹配成功的子串的索引
#(0, 5)


search 方法:从任何位置开始查找,一次匹配
# search 方法用于查找字符串的任何位置,它也是一次匹配,只要找到了一个匹配的结果就返回,而不是查找所有匹配的结果,它的一般使用形式如下:
# 当匹配成功时,返回一个 Match 对象,如果没有匹配上,则返回 None。
pattern = re.compile(‘\d+‘)
m = pattern.search(‘one12twothree34four‘) # 这里如果使用 match 方法则不匹配
# <_sre.SRE_Match object at 0x10cc03ac0>
m.group()
# ‘12‘

findall 方法:全部匹配,返回列表
# 搜索整个字符串,获得所有匹配的结果。
pattern = re.compile(r‘\d+‘) # 查找数字
result1 = pattern.findall(‘hello 123456 789‘)
result2 = pattern.findall(‘one1two2three3four4‘, 0, 10)
print (result1)
print (result2)
# [‘123456‘, ‘789‘]
# [‘1‘, ‘2‘]

finditer 方法:全部匹配,返回迭代器

split 方法:分割字符串,返回列表

p = re.compile(r‘[\s\,\;]+‘)
print (p.split(‘a,b;; c d‘))
# [‘a‘, ‘b‘, ‘c‘, ‘d‘]

sub 方法:替换
p = re.compile(r‘(\w+) (\w+)‘) # \w = [A-Za-z0-9]
s = ‘hello 123, hello 456‘

print (p.sub(r‘hello world‘, s)) # 使用 ‘hello world‘ 替换 ‘hello 123‘ 和 ‘hello 456‘

xpath
表达式
nodename 选取此节点的所有子节点。
/ 从根节点选取。
// 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。
. 选取当前节点。
.. 选取当前节点的父节点。
@ 选取属性。

路径
bookstore 选取 bookstore 元素的所有子节点。
/bookstore 选取根元素 bookstore。注释:假如路径起始于正斜杠( / ),则此路径始终代表到某元素的绝对路径!
bookstore/book 选取属于 bookstore 的子元素的所有 book 元素。
//book 选取所有 book 子元素,而不管它们在文档中的位置。
bookstore//book 选择属于 bookstore 元素的后代的所有 book 元素,而不管它们位于 bookstore 之下的什么位置。
//@lang 选取名为 lang 的所有属性。

谓语
/bookstore/book[1] 选取属于 bookstore 子元素的第一个 book 元素。
/bookstore/book[last()] 选取属于 bookstore 子元素的最后一个 book 元素。
/bookstore/book[last()-1] 选取属于 bookstore 子元素的倒数第二个 book 元素。
/bookstore/book[position()<3] 选取最前面的两个属于 bookstore 元素的子元素的 book 元素。
//title[@lang] 选取所有拥有名为 lang 的属性的 title 元素。
//title[@lang=’eng’] 选取所有 title 元素,且这些元素拥有值为 eng 的 lang 属性。
/bookstore/book[price>35.00] 选取 bookstore 元素的所有 book 元素,且其中的 price 元素的值须大于 35.00。
/bookstore/book[price>35.00]/title 选取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值须大于 35.00。

多个路径
//book/title | //book/price 选取 book 元素的所有 title 和 price 元素。
//title | //price 选取文档中的所有 title 和 price 元素。
/bookstore/book/title | //price 选取属于 bookstore 元素的 book 元素的所有 title 元素,以及文档中所有的 price 元素。

lxml库
使用xpath之前需要先将响应信息转换为xml结构
安装: pip install lxml
# 1. 从字符串中解析
# 使用 lxml 的 etree 库
from lxml import etree

text = ‘‘‘
<div>
<ul>
<li class="item-0"><a href="link1.html">first item</a></li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-inactive"><a href="link3.html">third item</a></li>
<li class="item-1"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a> # 注意,此处缺少一个 </li> 闭合标签
</ul>
</div>
‘‘‘
#利用etree.HTML,将字符串解析为HTML文档
# lxml可以修正缺少的html标签
html = etree.HTML(text)
# 按字符串序列化HTML文档
result = etree.tostring(html)
print(result)

# 2. 从文件中解析
from lxml import etree

# 读取外部文件 hello.html
html = etree.parse(‘./hello.html‘)
result = etree.tostring(html, pretty_print=True)
# 谓语 [last()] 可以找到最后一个元素
result = html.xpath(‘//li[last()]/a/@href‘) # 获取href属性的值
result = html.xpath(‘//li[last()-1]/a‘).text # 获取a标签的文本
result = html.xpath(‘//*[@class="bold"]‘).tag # 获取标签名
print(result)


CSS (beautifulsoup4)
lxml 只会局部遍历,而Beautiful Soup 是基于HTML DOM的,会载入整个文档,解析整个DOM树,因此时间和内存开销都会大很多,所以性能要低于lxml。
安装: pip install beautifulsoup4
基本使用
from bs4 import BeautifulSoup

html = """
<html><head><title>The Dormouse‘s story</title></head>
<body>
<p class="title" name="dromouse"><b>The Dormouse‘s story</b></p>
<p class="story">Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" class="sister" id="link1"><!-- Elsie --></a>,
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>
<p class="story">...</p>
"""
#创建 Beautiful Soup 对象
# soup = BeautifulSoup(html)
soup = BeautifulSoup(html,‘lxml‘) # 指定lxml解析器
# 打开本地 HTML 文件的方式来创建对象
# soup = BeautifulSoup(open(‘index.html‘))
# 格式化输出 soup 对象的内容
print soup.prettify()

bs4 的4大对象种类
Tag 标签
# 获取各类标签对象
print soup.title
# <title>The Dormouse‘s story</title>
print soup.head
# <head><title>The Dormouse‘s story</title></head>
print soup.a
# <a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>
print soup.p
# <p class="title" name="dromouse"><b>The Dormouse‘s story</b></p>
print type(soup.p)
# <class ‘bs4.element.Tag‘>

# 对于 Tag,它有两个重要的属性,是 name 和 attrs
print soup.head.name # 标签的名字
# head
print soup.p.attrs # 标签的所有属性
# {‘class‘: [‘title‘], ‘name‘: ‘dromouse‘}

print soup.p[‘class‘] # 获取标签的某一个属性的值
# soup.p.get(‘class‘) # 等价于上面
# [‘title‘] #还可以利用get方法,传入属性的名称,二者是等价的

BeautifulSoup 整体页面DOM对象, 它是一个特殊的Tag对象
print type(soup.name)
# <type ‘unicode‘>
print soup.name
# [document]
print soup.attrs # 文档本身的属性为空
# {}

NavigableString 标签的内容
print soup.p.string
# The Dormouse‘s story
print type(soup.p.string)
# In [13]: <class ‘bs4.element.NavigableString‘>

Comment 注释对象 它是一个特殊的NavigableString
print soup.a
# <a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>
print soup.a.string
# Elsie
print type(soup.a.string)
# <class ‘bs4.element.Comment‘>

bs4 搜索
直接子节点 :.contents .children
# .content 属性可以将tag的子节点以列表的方式输出
print soup.head.contents
# [<title>The Dormouse‘s story</title>]
print soup.head.contents[0]
# <title>The Dormouse‘s story</title>
print soup.head.children
#<listiterator object at 0x7f71457f5710>
for child in soup.body.children:
print child

find_all(name, attrs, recursive, text, **kwargs) 返回list
# 1. 通过标签名查找
soup.find_all(‘b‘) # 查找所有b标签, 返回列表
# [<b>The Dormouse‘s story</b>]

# 2. 通过属性查找
soup.find_all(id=‘link2‘)
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]

select() 返回list
# 1. 通过标签名查找
print soup.select(‘title‘)
#[<title>The Dormouse‘s story</title>]

# 2. 通过类名查找
print soup.select(‘.sister‘)
# [<a class="sister" href="http://example.com/ </a>]

# 3. 通过id查找
print soup.select(‘#link1‘)
#[<a class="sister" href="http://example.com/elsie" id="link1">链接</a>]

# 4. 通过属性查找
print soup.select(‘a[class="sister"]‘)
#[<a class="sister" href="http://example.com/elsie" id="link1"><!-- --></a>,
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
print soup.select(‘a[href="http://example.com/elsie"]‘)
#[<a class="sister" href="http://example.com/elsie" id="link1"><!-- --></a>]

# 5. 组合查找
print soup.select(‘p #link1‘)
#[<a class="sister" href="http://example.com/elsie" id="link1"><!-- --></a>]

# 6. 获取内容
soup = BeautifulSoup(html, ‘lxml‘)
print type(soup.select(‘title‘))
print soup.select(‘title‘)[0].get_text()

for title in soup.select(‘title‘):
print title.get_text()

结构化数据处理(json)
使用json模块进行json字符串与python对象之间相互转换的操作
json.loads() json 转 python
import json

strList = ‘[1, 2, 3, 4]‘ # json的数组
strDict = ‘{"city": "北京", "name": "大猫"}‘ # json的对象
json.loads(strList) # json字符串转python列表
# [1, 2, 3, 4]
json.loads(strDict) # json数据自动按Unicode存储
# {u‘city‘: u‘\u5317\u4eac‘, u‘name‘: u‘\u5927\u732b‘}

json.dumps() python 转 json
import json
import chardet # chardet是一个非常优秀的编码识别模块,可通过pip安装

listStr = [1, 2, 3, 4]
tupleStr = (1, 2, 3, 4)
dictStr = {"city": "北京", "name": "大猫"}
json.dumps(listStr)
# ‘[1, 2, 3, 4]‘
json.dumps(tupleStr)
# ‘[1, 2, 3, 4]‘

# 注意:json.dumps() 序列化时默认使用的ascii编码
# 添加参数 ensure_ascii=False 禁用ascii编码,按utf-8编码
# chardet.detect()返回字典, 其中confidence是检测精确度
json.dumps(dictStr)
# ‘{"city": "\\u5317\\u4eac", "name": "\\u5927\\u5218"}‘
chardet.detect(json.dumps(dictStr)) # 检测编码
# {‘confidence‘: 1.0, ‘encoding‘: ‘ascii‘}
print json.dumps(dictStr, ensure_ascii=False)
# {"city": "北京", "name": "大刘"}
chardet.detect(json.dumps(dictStr, ensure_ascii=False))
# {‘confidence‘: 0.99, ‘encoding‘: ‘utf-8‘}

json.load() 从文件中直接转python
读取文件中json形式的字符串元素 转化成python类型
strList = json.load(open("listStr.json"))
# [{u‘city‘: u‘\u5317\u4eac‘}, {u‘name‘: u‘\u5927\u5218‘}]
strDict = json.load(open("dictStr.json"))
# {u‘city‘: u‘\u5317\u4eac‘, u‘name‘: u‘\u5927\u5218‘}

json.dump() python转json写入文件
将Python内置类型序列化为json对象后写入文件
listStr = [{"city": "北京"}, {"name": "大刘"}]
json.dump(listStr, open("listStr.json","w"), ensure_ascii=False)

dictStr = {"city": "北京", "name": "大刘"}
json.dump(dictStr, open("dictStr.json","w"), ensure_ascii=False)

xpath抓取案例
糗事百科
#coding=utf-8
import requests
from retrying import retry
from lxml import etree

class Qiubai_spider():
def __init__(self):
self.url = "http://www.qiushibaike.com/8hr/page/{}/" # 链接模型
self.headers = {
"User-Agent":"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1 Trident/5.0;"
}

@retry(stop_max_attempt_number=5) #调用retry,当assert出错时候,重复请求5次
def parse_url(self,url):
response = requests.get(url,timeout=10,headers=self.headers) #请求url
assert response.status_code==200 #当响应码不是200时候,做断言报错处理
print(url)
return etree.HTML(response.text) #返回etree之后的html

def parse_content(self,html):
item_temp = html.xpath("//div[@class=‘article block untagged mb15‘]")
print(len(item_temp))
for item in item_temp:
#获取用户头像地址
avatar = item.xpath("./div[1]/a[1]/img/@src")[0] if len(item.xpath("./div[1]/a[1]/img/@src"))>0 else None
#为头像地址添加前缀
if avatar is not None and not avatar.startswith("http:"):
avatar = "http:"+avatar
name = item.xpath("./div[1]/a[2]/h2/text()")[0] #获取用户名
content = item.xpath("./a[@class=‘contentHerf‘]/div/span/text()")[0] #获取内容
star_number = item.xpath("./div[@class=‘stats‘]/span[1]/i/text()")[0] #获取点赞数
comment_number = item.xpath("./div[@class=‘stats‘]/span[2]/a/i/text()")[0] #获取评论数
print("*"*100)

def run(self):
‘‘‘函数的主要逻辑实现
‘‘‘
url = self.url.format(1) #获取到url
html = self.parse_url(url) #请求url
self.parse_content(html) #解析页面内容并把内容存入内容队列

if __name__ == "__main__":
qiubai = Qiubai_spider()
qiubai.run()


天气网
#encoding: utf-8

import requests
from bs4 import BeautifulSoup
from pyecharts import Bar

ALL_DATA = []

def parse_page(url):
headers = {
‘User-Agent‘: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36"
}
response = requests.get(url,headers=headers)
text = response.content.decode(‘utf-8‘)
# html5lib
# pip install html5lib
soup = BeautifulSoup(text,‘html5lib‘)
conMidtab = soup.find(‘div‘,class_=‘conMidtab‘)
tables = conMidtab.find_all(‘table‘)
for table in tables:
trs = table.find_all(‘tr‘)[2:]
for index,tr in enumerate(trs):
tds = tr.find_all(‘td‘)
city_td = tds[0]
if index == 0:
city_td = tds[1]
city = list(city_td.stripped_strings)[0]
temp_td = tds[-2]
min_temp = list(temp_td.stripped_strings)[0]
ALL_DATA.append({"city":city,"min_temp":int(min_temp)})
# print({"city":city,"min_temp":int(min_temp)})

def main():
urls = [
‘http://www.weather.com.cn/textFC/hb.shtml‘,
‘http://www.weather.com.cn/textFC/db.shtml‘,
‘http://www.weather.com.cn/textFC/hd.shtml‘,
‘http://www.weather.com.cn/textFC/hz.shtml‘,
‘http://www.weather.com.cn/textFC/hn.shtml‘,
‘http://www.weather.com.cn/textFC/xb.shtml‘,
‘http://www.weather.com.cn/textFC/xn.shtml‘,
‘http://www.weather.com.cn/textFC/gat.shtml‘
]
for url in urls:
parse_page(url)

# 分析数据
# 根据最低气温进行排序
ALL_DATA.sort(key=lambda data:data[‘min_temp‘])

data = ALL_DATA[0:10]
cities = list(map(lambda x:x[‘city‘],data))
temps = list(map(lambda x:x[‘min_temp‘],data))
# pyecharts
# pip install pyecharts
chart = Bar("中国天气最低气温排行榜")
chart.add(‘‘,cities,temps)
chart.render(‘temperature.html‘)


if __name__ == ‘__main__‘:
main()
# ALL_DATA = [
# {"city": "北京", ‘min_temp‘: 0},
# {"city": "天津", ‘min_temp‘: -8},
# {"city": "石家庄", ‘min_temp‘: -10}
# ]
#
# ALL_DATA.sort(key=lambda data:data[‘min_temp‘])

多线程
Queue 常用方法
Queue是python中的标准库,可以直接import Queue引用;队列是线程间最常用的交换数据的形式
Queue是线程安全的
Queue.qsize() # 返回队列的大小
Queue.empty() # 如果队列为空,返回True,反之False
Queue.full() # 如果队列满了,返回True,反之False
Queue.full # 与 maxsize 大小对应
Queue.get([block[, timeout]]) #获取队列,timeout等待时间

创建队列
import Queue
myqueue = Queue.Queue(maxsize = 10)

插入队列
myqueue.put(10)

将一个值从队列中取出
myqueue.get()

多线程抓取案例
# coding=utf-8
import requests
from lxml import etree
import json
from queue import Queue
import threading

class Qiubai:
def __init__(self):
self.headers = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWeb\
Kit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36"}
self.url_queue = Queue() # url队列
self.html_queue =Queue() # html响应信息队列
self.content_queue = Queue() # 内容队列


def get_total_url(self):
‘‘‘
获取了所有的页面url,并且返回urllist
return :list
‘‘‘
url_temp = ‘https://www.qiushibaike.com/8hr/page/{}/‘ # url模板
for i in range(1,36):
self.url_queue.put(url_temp.format(i)) # url入队

def parse_url(self):
‘‘‘
一个发送请求,获取响应,同时etree处理html
‘‘‘
while self.url_queue.not_empty: # 判断url队列不为空
url = self.url_queue.get() # 从队列中取出一个url
print("parsing url:", url)
response = requests.get(url,headers=self.headers,timeout=10) # 发送请求
html = response.content.decode() #获取html字符串
html = etree.HTML(html) #获取element 类型的html
self.html_queue.put(html) # html响应入队
self.url_queue.task_done() # task_done的时候,队列计数减一

def get_content(self):
‘‘‘
:param url:
:return: 一个list,包含一个url对应页面的所有段子的所有内容的列表
‘‘‘
while self.html_queue.not_empty: # 判断html响应队列不为空
html = self.html_queue.get() # 从队列中取出一份html响应信息
# 解析内容
total_div = html.xpath(‘//div[@class="article block untagged mb15"]‘) #返回divelememt的一个列表
itme_list = [] # 用于保存最终所有的信息
for i in total_div: #遍历div标枪,获取糗事百科每条的内容的全部信息
author_img = i.xpath(‘./div[@class="author clearfix"]/a[1]/img/@src‘)
author_img = "https:" + author_img[0] if len(author_img) > 0 else None
author_name = i.xpath(‘./div[@class="author clearfix"]/a[2]/h2/text()‘)
author_name = author_name[0] if len(author_name) > 0 else None
author_href = i.xpath(‘./div[@class="author clearfix"]/a[1]/@href‘)
author_href = "https://www.qiushibaike.com" + author_href[0] if len(author_href) > 0 else None
author_gender = i.xpath(‘./div[@class="author clearfix"]//div/@class‘)
author_gender = author_gender[0].split(" ")[-1].replace("Icon", "") if len(author_gender) > 0 else None
author_age = i.xpath(‘./div[@class="author clearfix"]//div/text()‘)
author_age = author_age[0] if len(author_age) > 0 else None
content = i.xpath(‘./a[@class="contentHerf"]/div/span/text()‘)
content_vote = i.xpath(‘./div[@class="stats"]/span[1]/i/text()‘)
content_vote = content_vote[0] if len(content_vote) > 0 else None
content_comment_numbers = i.xpath(‘./div[@class="stats"]/span[2]/a/i/text()‘)
content_comment_numbers = content_comment_numbers[0] if len(content_comment_numbers) > 0 else None
hot_comment_author = i.xpath(‘./a[@class="indexGodCmt"]/div/span[last()]/text()‘)
hot_comment_author = hot_comment_author[0] if len(hot_comment_author) > 0 else None
hot_comment = i.xpath(‘./a[@class="indexGodCmt"]/div/div/text()‘)
hot_comment = hot_comment[0].replace("\n:", "").replace("\n", "") if len(hot_comment) > 0 else None
hot_comment_like_num = i.xpath(‘./a[@class="indexGodCmt"]/div/div/div/text()‘)
hot_comment_like_num = hot_comment_like_num[-1].replace("\n", "") if len(hot_comment_like_num) > 0 else None
item = dict(
author_name=author_name,
author_img=author_img,
author_href=author_href,
author_gender=author_gender,
author_age=author_age,
content=content,
content_vote=content_vote,
content_comment_numbers=content_comment_numbers,
hot_comment=hot_comment,
hot_comment_author=hot_comment_author,
hot_comment_like_num=hot_comment_like_num
)
item_list.append(item)
self.content_queue.put(items) # 内容入队
self.html_queue.task_done() # task_done的时候,队列计数-1

def save_items(self):
‘‘‘
保存items
:param items:列表
‘‘‘
while self.content_queue.not_empty: # 判断内容队列不为空
items = self.content_queue.get() # 从队列中取出一份内容, list类型
f = open("qiubai.txt","a")
for i in items:
json.dump(i, f, ensure_ascii=False, indent=2) # python对象转json文件
# f.write(json.dumps(i))
f.close()
self.content_queue.task_done() # 队列计数-1

def run(self):
# 1.获取url list
# url_list = self.get_total_url()
thread_list = [] # 线程池
thread_url = threading.Thread(target=self.get_total_url) # 创建生产url的线程
thread_list.append(thread_url)
#发送网络请求
for i in range(10):
thread_parse = threading.Thread(target=self.parse_url) # 创建请求url的线程
thread_list.append(thread_parse)
# 解析数据
thread_get_content = threading.Thread(target=self.get_content) # 创建解析响应的线程
thread_list.append(thread_get_content)
# 保存数据
thread_save = threading.Thread(target=self.save_items) # 创建保存内容的线程
thread_list.append(thread_save)

for t in thread_list:
t.setDaemon(True) #为每个进程设置为后台进程,效果是主进程退出子进程也会退出
t.start() #为了解决程序结束无法退出的问题

self.url_queue.join() #让主线程等待,所有的队列为空的时候才能退出
self.html_queue.join()
self.content_queue.join()

if __name__ == "__main__":
qiubai = Qiubai()
qiubai.run()
————————————————
版权声明:本文为CSDN博主「DeltaTime」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/bua200720411091/article/details/93378433

非结构化数据处理(文本)
正则
match 方法:从起始位置开始查找,一次匹配
# match 方法用于查找字符串的头部(也可以指定起始位置),它是一次匹配,只要找到了一个匹配的结果就返回,而不是查找所有匹配的结果。它的一般使用形式如下:
# 其中,string 是待匹配的字符串,pos 和 endpos 是可选参数,指定字符串的起始和终点位置,默认值分别是 0 和 len (字符串长度)。
# 不指定 pos 和 endpos 时,match 方法默认匹配字符串的头部。
#当匹配成功时,返回一个 Match 对象,如果没有匹配上,则返回 None。
import re
pattern = re.compile(r‘\d+‘) # 用于匹配至少一个数字
m = pattern.match(‘one12twothree34four‘) # 查找头部,没有匹配
print (m)
m = pattern.match(‘one12twothree34four‘, 2, 10) # 从‘e‘的位置开始匹配,没有匹配
m = pattern.match(‘one12twothree34four‘, 3, 10) # 从‘1‘的位置开始匹配,正好匹配

group()分组匹配, 提取数据
pattern = re.compile(r‘([a-z]+) ([a-z]+)‘, re.I) # re.I 表示忽略大小写
m = pattern.match(‘Hello World Wide Web‘)
print (m) # 匹配成功,返回一个 Match 对象
#<_sre.SRE_Match object at 0x10bea83e8>

m.group(0) # 返回匹配成功的整个子串
#‘Hello World‘
m.group(1) # 返回第一个分组匹配成功的子串
#‘Hello‘
m.group(2) # 返回第二个分组匹配成功的子串
#‘World‘
m.span(0) # 返回匹配成功的整个子串的索引
#(0, 11)
m.span(1) # 返回第一个分组匹配成功的子串的索引
#(0, 5)

search 方法:从任何位置开始查找,一次匹配
# search 方法用于查找字符串的任何位置,它也是一次匹配,只要找到了一个匹配的结果就返回,而不是查找所有匹配的结果,它的一般使用形式如下:
# 当匹配成功时,返回一个 Match 对象,如果没有匹配上,则返回 None。
pattern = re.compile(‘\d+‘)
m = pattern.search(‘one12twothree34four‘) # 这里如果使用 match 方法则不匹配
# <_sre.SRE_Match object at 0x10cc03ac0>
m.group()
# ‘12‘

findall 方法:全部匹配,返回列表
# 搜索整个字符串,获得所有匹配的结果。
pattern = re.compile(r‘\d+‘) # 查找数字
result1 = pattern.findall(‘hello 123456 789‘)
result2 = pattern.findall(‘one1two2three3four4‘, 0, 10)
print (result1)
print (result2)
# [‘123456‘, ‘789‘]
# [‘1‘, ‘2‘]

finditer 方法:全部匹配,返回迭代器

split 方法:分割字符串,返回列表

p = re.compile(r‘[\s\,\;]+‘)
print (p.split(‘a,b;; c d‘))
# [‘a‘, ‘b‘, ‘c‘, ‘d‘]

sub 方法:替换
p = re.compile(r‘(\w+) (\w+)‘) # \w = [A-Za-z0-9]
s = ‘hello 123, hello 456‘

print (p.sub(r‘hello world‘, s)) # 使用 ‘hello world‘ 替换 ‘hello 123‘ 和 ‘hello 456‘

xpath
表达式
nodename 选取此节点的所有子节点。
/ 从根节点选取。
// 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。
. 选取当前节点。
.. 选取当前节点的父节点。
@ 选取属性。

路径
bookstore 选取 bookstore 元素的所有子节点。
/bookstore 选取根元素 bookstore。注释:假如路径起始于正斜杠( / ),则此路径始终代表到某元素的绝对路径!
bookstore/book 选取属于 bookstore 的子元素的所有 book 元素。
//book 选取所有 book 子元素,而不管它们在文档中的位置。
bookstore//book 选择属于 bookstore 元素的后代的所有 book 元素,而不管它们位于 bookstore 之下的什么位置。
//@lang 选取名为 lang 的所有属性。

谓语
/bookstore/book[1] 选取属于 bookstore 子元素的第一个 book 元素。
/bookstore/book[last()] 选取属于 bookstore 子元素的最后一个 book 元素。
/bookstore/book[last()-1] 选取属于 bookstore 子元素的倒数第二个 book 元素。
/bookstore/book[position()<3] 选取最前面的两个属于 bookstore 元素的子元素的 book 元素。
//title[@lang] 选取所有拥有名为 lang 的属性的 title 元素。
//title[@lang=’eng’] 选取所有 title 元素,且这些元素拥有值为 eng 的 lang 属性。
/bookstore/book[price>35.00] 选取 bookstore 元素的所有 book 元素,且其中的 price 元素的值须大于 35.00。
/bookstore/book[price>35.00]/title 选取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值须大于 35.00。

多个路径
//book/title | //book/price 选取 book 元素的所有 title 和 price 元素。
//title | //price 选取文档中的所有 title 和 price 元素。
/bookstore/book/title | //price 选取属于 bookstore 元素的 book 元素的所有 title 元素,以及文档中所有的 price 元素。

lxml库
使用xpath之前需要先将响应信息转换为xml结构
安装: pip install lxml
# 1. 从字符串中解析
# 使用 lxml 的 etree 库
from lxml import etree

text = ‘‘‘
<div>
<ul>
<li class="item-0"><a href="link1.html">first item</a></li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-inactive"><a href="link3.html">third item</a></li>
<li class="item-1"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a> # 注意,此处缺少一个 </li> 闭合标签
</ul>
</div>
‘‘‘
#利用etree.HTML,将字符串解析为HTML文档
# lxml可以修正缺少的html标签
html = etree.HTML(text)
# 按字符串序列化HTML文档
result = etree.tostring(html)
print(result)

# 2. 从文件中解析
from lxml import etree

# 读取外部文件 hello.html
html = etree.parse(‘./hello.html‘)
result = etree.tostring(html, pretty_print=True)
# 谓语 [last()] 可以找到最后一个元素
result = html.xpath(‘//li[last()]/a/@href‘) # 获取href属性的值
result = html.xpath(‘//li[last()-1]/a‘).text # 获取a标签的文本
result = html.xpath(‘//*[@class="bold"]‘).tag # 获取标签名
print(result)


CSS (beautifulsoup4)
lxml 只会局部遍历,而Beautiful Soup 是基于HTML DOM的,会载入整个文档,解析整个DOM树,因此时间和内存开销都会大很多,所以性能要低于lxml。
安装: pip install beautifulsoup4
基本使用
from bs4 import BeautifulSoup

html = """
<html><head><title>The Dormouse‘s story</title></head>
<body>
<p class="title" name="dromouse"><b>The Dormouse‘s story</b></p>
<p class="story">Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" class="sister" id="link1"><!-- Elsie --></a>,
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>
<p class="story">...</p>
"""
#创建 Beautiful Soup 对象
# soup = BeautifulSoup(html)
soup = BeautifulSoup(html,‘lxml‘) # 指定lxml解析器
# 打开本地 HTML 文件的方式来创建对象
# soup = BeautifulSoup(open(‘index.html‘))
# 格式化输出 soup 对象的内容
print soup.prettify()

bs4 的4大对象种类
Tag 标签
# 获取各类标签对象
print soup.title
# <title>The Dormouse‘s story</title>
print soup.head
# <head><title>The Dormouse‘s story</title></head>
print soup.a
# <a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>
print soup.p
# <p class="title" name="dromouse"><b>The Dormouse‘s story</b></p>
print type(soup.p)
# <class ‘bs4.element.Tag‘>

# 对于 Tag,它有两个重要的属性,是 name 和 attrs
print soup.head.name # 标签的名字
# head
print soup.p.attrs # 标签的所有属性
# {‘class‘: [‘title‘], ‘name‘: ‘dromouse‘}

print soup.p[‘class‘] # 获取标签的某一个属性的值
# soup.p.get(‘class‘) # 等价于上面
# [‘title‘] #还可以利用get方法,传入属性的名称,二者是等价的

BeautifulSoup 整体页面DOM对象, 它是一个特殊的Tag对象
print type(soup.name)
# <type ‘unicode‘>
print soup.name
# [document]
print soup.attrs # 文档本身的属性为空
# {}

NavigableString 标签的内容
print soup.p.string
# The Dormouse‘s story
print type(soup.p.string)
# In [13]: <class ‘bs4.element.NavigableString‘>

Comment 注释对象 它是一个特殊的NavigableString
print soup.a
# <a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>
print soup.a.string
# Elsie
print type(soup.a.string)
# <class ‘bs4.element.Comment‘>

bs4 搜索
直接子节点 :.contents .children
# .content 属性可以将tag的子节点以列表的方式输出
print soup.head.contents
# [<title>The Dormouse‘s story</title>]
print soup.head.contents[0]
# <title>The Dormouse‘s story</title>
print soup.head.children
#<listiterator object at 0x7f71457f5710>
for child in soup.body.children:
print child

find_all(name, attrs, recursive, text, **kwargs) 返回list
# 1. 通过标签名查找
soup.find_all(‘b‘) # 查找所有b标签, 返回列表
# [<b>The Dormouse‘s story</b>]

# 2. 通过属性查找
soup.find_all(id=‘link2‘)
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]

select() 返回list
# 1. 通过标签名查找
print soup.select(‘title‘)
#[<title>The Dormouse‘s story</title>]

# 2. 通过类名查找
print soup.select(‘.sister‘)
# [<a class="sister" href="http://example.com/ </a>]

# 3. 通过id查找
print soup.select(‘#link1‘)
#[<a class="sister" href="http://example.com/elsie" id="link1">链接</a>]

# 4. 通过属性查找
print soup.select(‘a[class="sister"]‘)
#[<a class="sister" href="http://example.com/elsie" id="link1"><!-- --></a>,
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
print soup.select(‘a[href="http://example.com/elsie"]‘)
#[<a class="sister" href="http://example.com/elsie" id="link1"><!-- --></a>]

# 5. 组合查找
print soup.select(‘p #link1‘)
#[<a class="sister" href="http://example.com/elsie" id="link1"><!-- --></a>]

# 6. 获取内容
soup = BeautifulSoup(html, ‘lxml‘)
print type(soup.select(‘title‘))
print soup.select(‘title‘)[0].get_text()

for title in soup.select(‘title‘):
print title.get_text()

结构化数据处理(json)
使用json模块进行json字符串与python对象之间相互转换的操作
json.loads() json 转 python
import json

strList = ‘[1, 2, 3, 4]‘ # json的数组
strDict = ‘{"city": "北京", "name": "大猫"}‘ # json的对象
json.loads(strList) # json字符串转python列表
# [1, 2, 3, 4]
json.loads(strDict) # json数据自动按Unicode存储
# {u‘city‘: u‘\u5317\u4eac‘, u‘name‘: u‘\u5927\u732b‘}

json.dumps() python 转 json
import json
import chardet # chardet是一个非常优秀的编码识别模块,可通过pip安装

listStr = [1, 2, 3, 4]
tupleStr = (1, 2, 3, 4)
dictStr = {"city": "北京", "name": "大猫"}
json.dumps(listStr)
# ‘[1, 2, 3, 4]‘
json.dumps(tupleStr)
# ‘[1, 2, 3, 4]‘

# 注意:json.dumps() 序列化时默认使用的ascii编码
# 添加参数 ensure_ascii=False 禁用ascii编码,按utf-8编码
# chardet.detect()返回字典, 其中confidence是检测精确度
json.dumps(dictStr)
# ‘{"city": "\\u5317\\u4eac", "name": "\\u5927\\u5218"}‘
chardet.detect(json.dumps(dictStr)) # 检测编码
# {‘confidence‘: 1.0, ‘encoding‘: ‘ascii‘}
print json.dumps(dictStr, ensure_ascii=False)
# {"city": "北京", "name": "大刘"}
chardet.detect(json.dumps(dictStr, ensure_ascii=False))
# {‘confidence‘: 0.99, ‘encoding‘: ‘utf-8‘}

json.load() 从文件中直接转python
读取文件中json形式的字符串元素 转化成python类型
strList = json.load(open("listStr.json"))
# [{u‘city‘: u‘\u5317\u4eac‘}, {u‘name‘: u‘\u5927\u5218‘}]
strDict = json.load(open("dictStr.json"))
# {u‘city‘: u‘\u5317\u4eac‘, u‘name‘: u‘\u5927\u5218‘}

json.dump() python转json写入文件
将Python内置类型序列化为json对象后写入文件
listStr = [{"city": "北京"}, {"name": "大刘"}]
json.dump(listStr, open("listStr.json","w"), ensure_ascii=False)

dictStr = {"city": "北京", "name": "大刘"}
json.dump(dictStr, open("dictStr.json","w"), ensure_ascii=False)

xpath抓取案例
糗事百科
#coding=utf-8
import requests
from retrying import retry
from lxml import etree

class Qiubai_spider():
def __init__(self):
self.url = "http://www.qiushibaike.com/8hr/page/{}/" # 链接模型
self.headers = {
"User-Agent":"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1 Trident/5.0;"
}

@retry(stop_max_attempt_number=5) #调用retry,当assert出错时候,重复请求5次
def parse_url(self,url):
response = requests.get(url,timeout=10,headers=self.headers) #请求url
assert response.status_code==200 #当响应码不是200时候,做断言报错处理
print(url)
return etree.HTML(response.text) #返回etree之后的html

def parse_content(self,html):
item_temp = html.xpath("//div[@class=‘article block untagged mb15‘]")
print(len(item_temp))
for item in item_temp:
#获取用户头像地址
avatar = item.xpath("./div[1]/a[1]/img/@src")[0] if len(item.xpath("./div[1]/a[1]/img/@src"))>0 else None
#为头像地址添加前缀
if avatar is not None and not avatar.startswith("http:"):
avatar = "http:"+avatar
name = item.xpath("./div[1]/a[2]/h2/text()")[0] #获取用户名
content = item.xpath("./a[@class=‘contentHerf‘]/div/span/text()")[0] #获取内容
star_number = item.xpath("./div[@class=‘stats‘]/span[1]/i/text()")[0] #获取点赞数
comment_number = item.xpath("./div[@class=‘stats‘]/span[2]/a/i/text()")[0] #获取评论数
print("*"*100)

def run(self):
‘‘‘函数的主要逻辑实现
‘‘‘
url = self.url.format(1) #获取到url
html = self.parse_url(url) #请求url
self.parse_content(html) #解析页面内容并把内容存入内容队列

if __name__ == "__main__":
qiubai = Qiubai_spider()
qiubai.run()


天气网
#encoding: utf-8

import requests
from bs4 import BeautifulSoup
from pyecharts import Bar

ALL_DATA = []

def parse_page(url):
headers = {
‘User-Agent‘: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36"
}
response = requests.get(url,headers=headers)
text = response.content.decode(‘utf-8‘)
# html5lib
# pip install html5lib
soup = BeautifulSoup(text,‘html5lib‘)
conMidtab = soup.find(‘div‘,class_=‘conMidtab‘)
tables = conMidtab.find_all(‘table‘)
for table in tables:
trs = table.find_all(‘tr‘)[2:]
for index,tr in enumerate(trs):
tds = tr.find_all(‘td‘)
city_td = tds[0]
if index == 0:
city_td = tds[1]
city = list(city_td.stripped_strings)[0]
temp_td = tds[-2]
min_temp = list(temp_td.stripped_strings)[0]
ALL_DATA.append({"city":city,"min_temp":int(min_temp)})
# print({"city":city,"min_temp":int(min_temp)})

def main():
urls = [
‘http://www.weather.com.cn/textFC/hb.shtml‘,
‘http://www.weather.com.cn/textFC/db.shtml‘,
‘http://www.weather.com.cn/textFC/hd.shtml‘,
‘http://www.weather.com.cn/textFC/hz.shtml‘,
‘http://www.weather.com.cn/textFC/hn.shtml‘,
‘http://www.weather.com.cn/textFC/xb.shtml‘,
‘http://www.weather.com.cn/textFC/xn.shtml‘,
‘http://www.weather.com.cn/textFC/gat.shtml‘
]
for url in urls:
parse_page(url)

# 分析数据
# 根据最低气温进行排序
ALL_DATA.sort(key=lambda data:data[‘min_temp‘])

data = ALL_DATA[0:10]
cities = list(map(lambda x:x[‘city‘],data))
temps = list(map(lambda x:x[‘min_temp‘],data))
# pyecharts
# pip install pyecharts
chart = Bar("中国天气最低气温排行榜")
chart.add(‘‘,cities,temps)
chart.render(‘temperature.html‘)


if __name__ == ‘__main__‘:
main()
# ALL_DATA = [
# {"city": "北京", ‘min_temp‘: 0},
# {"city": "天津", ‘min_temp‘: -8},
# {"city": "石家庄", ‘min_temp‘: -10}
# ]
#
# ALL_DATA.sort(key=lambda data:data[‘min_temp‘])
# print(ALL_DATA)

多线程
Queue 常用方法
Queue是python中的标准库,可以直接import Queue引用;队列是线程间最常用的交换数据的形式
Queue是线程安全的
Queue.qsize() # 返回队列的大小
Queue.empty() # 如果队列为空,返回True,反之False
Queue.full() # 如果队列满了,返回True,反之False
Queue.full # 与 maxsize 大小对应
Queue.get([block[, timeout]]) #获取队列,timeout等待时间

创建队列
import Queue
myqueue = Queue.Queue(maxsize = 10)

插入队列
myqueue.put(10)

将一个值从队列中取出
myqueue.get()

多线程抓取案例
# coding=utf-8
import requests
from lxml import etree
import json
from queue import Queue
import threading

class Qiubai:
def __init__(self):
self.headers = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWeb\
Kit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36"}
self.url_queue = Queue() # url队列
self.html_queue =Queue() # html响应信息队列
self.content_queue = Queue() # 内容队列


def get_total_url(self):
‘‘‘
获取了所有的页面url,并且返回urllist
return :list
‘‘‘
url_temp = ‘https://www.qiushibaike.com/8hr/page/{}/‘ # url模板
for i in range(1,36):
self.url_queue.put(url_temp.format(i)) # url入队

def parse_url(self):
‘‘‘
一个发送请求,获取响应,同时etree处理html
‘‘‘
while self.url_queue.not_empty: # 判断url队列不为空
url = self.url_queue.get() # 从队列中取出一个url
print("parsing url:", url)
response = requests.get(url,headers=self.headers,timeout=10) # 发送请求
html = response.content.decode() #获取html字符串
html = etree.HTML(html) #获取element 类型的html
self.html_queue.put(html) # html响应入队
self.url_queue.task_done() # task_done的时候,队列计数减一

def get_content(self):
‘‘‘
:param url:
:return: 一个list,包含一个url对应页面的所有段子的所有内容的列表
‘‘‘
while self.html_queue.not_empty: # 判断html响应队列不为空
html = self.html_queue.get() # 从队列中取出一份html响应信息
# 解析内容
total_div = html.xpath(‘//div[@class="article block untagged mb15"]‘) #返回divelememt的一个列表
itme_list = [] # 用于保存最终所有的信息
for i in total_div: #遍历div标枪,获取糗事百科每条的内容的全部信息
author_img = i.xpath(‘./div[@class="author clearfix"]/a[1]/img/@src‘)
author_img = "https:" + author_img[0] if len(author_img) > 0 else None
author_name = i.xpath(‘./div[@class="author clearfix"]/a[2]/h2/text()‘)
author_name = author_name[0] if len(author_name) > 0 else None
author_href = i.xpath(‘./div[@class="author clearfix"]/a[1]/@href‘)
author_href = "https://www.qiushibaike.com" + author_href[0] if len(author_href) > 0 else None
author_gender = i.xpath(‘./div[@class="author clearfix"]//div/@class‘)
author_gender = author_gender[0].split(" ")[-1].replace("Icon", "") if len(author_gender) > 0 else None
author_age = i.xpath(‘./div[@class="author clearfix"]//div/text()‘)
author_age = author_age[0] if len(author_age) > 0 else None
content = i.xpath(‘./a[@class="contentHerf"]/div/span/text()‘)
content_vote = i.xpath(‘./div[@class="stats"]/span[1]/i/text()‘)
content_vote = content_vote[0] if len(content_vote) > 0 else None
content_comment_numbers = i.xpath(‘./div[@class="stats"]/span[2]/a/i/text()‘)
content_comment_numbers = content_comment_numbers[0] if len(content_comment_numbers) > 0 else None
hot_comment_author = i.xpath(‘./a[@class="indexGodCmt"]/div/span[last()]/text()‘)
hot_comment_author = hot_comment_author[0] if len(hot_comment_author) > 0 else None
hot_comment = i.xpath(‘./a[@class="indexGodCmt"]/div/div/text()‘)
hot_comment = hot_comment[0].replace("\n:", "").replace("\n", "") if len(hot_comment) > 0 else None
hot_comment_like_num = i.xpath(‘./a[@class="indexGodCmt"]/div/div/div/text()‘)
hot_comment_like_num = hot_comment_like_num[-1].replace("\n", "") if len(hot_comment_like_num) > 0 else None
item = dict(
author_name=author_name,
author_img=author_img,
author_href=author_href,
author_gender=author_gender,
author_age=author_age,
content=content,
content_vote=content_vote,
content_comment_numbers=content_comment_numbers,
hot_comment=hot_comment,
hot_comment_author=hot_comment_author,
hot_comment_like_num=hot_comment_like_num
)
item_list.append(item)
self.content_queue.put(items) # 内容入队
self.html_queue.task_done() # task_done的时候,队列计数-1

def save_items(self):
‘‘‘
保存items
:param items:列表
‘‘‘
while self.content_queue.not_empty: # 判断内容队列不为空
items = self.content_queue.get() # 从队列中取出一份内容, list类型
f = open("qiubai.txt","a")
for i in items:
json.dump(i, f, ensure_ascii=False, indent=2) # python对象转json文件
# f.write(json.dumps(i))
f.close()
self.content_queue.task_done() # 队列计数-1

def run(self):
# 1.获取url list
# url_list = self.get_total_url()
thread_list = [] # 线程池
thread_url = threading.Thread(target=self.get_total_url) # 创建生产url的线程
thread_list.append(thread_url)
#发送网络请求
for i in range(10):
thread_parse = threading.Thread(target=self.parse_url) # 创建请求url的线程
thread_list.append(thread_parse)
# 解析数据
thread_get_content = threading.Thread(target=self.get_content) # 创建解析响应的线程
thread_list.append(thread_get_content)
# 保存数据
thread_save = threading.Thread(target=self.save_items) # 创建保存内容的线程
thread_list.append(thread_save)

for t in thread_list:
t.setDaemon(True) #为每个进程设置为后台进程,效果是主进程退出子进程也会退出
t.start() #为了解决程序结束无法退出的问题

self.url_queue.join() #让主线程等待,所有的队列为空的时候才能退出
self.html_queue.join()
self.content_queue.join()

if __name__ == "__main__":
qiubai = Qiubai()
qiubai.run()
————————————————
版权声明:本文为CSDN博主「DeltaTime」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/bua200720411091/article/details/93378433

数据提取

标签:编码   header   创建   安全   mac   obj   数据处理   声明   主线程   

原文地址:https://www.cnblogs.com/llflifei/p/11910651.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!