依旧这些代码来自北理课程,之后会解释与学习相关知识
一:获取淘宝的商品与价格的代码:
import requests
import re
def getHTMLText(url):
try:
r = requests.get(url, timeout=30)
r.raise_for_status()
r.encoding = r.apparent_encoding
return r.text
except:
return ""
def parsePage(ilt, html):
try:
plt = re.findall(r'\"view_price\"\:\"[\d\.]*\"',html)
tlt = re.findall(r'\"raw_title\"\:\".*?\"',html)
for i in range(len(plt)):
price = eval(plt[i].split(':')[1])
title = eval(tlt[i].split(':')[1])
ilt.append([price , title])
except:
print("")
def printGoodsList(ilt):
tplt = "{:4}\t{:8}\t{:16}"
print(tplt.format("序号", "价格", "商品名称"))
count = 0
for g in ilt:
count = count + 1
print(tplt.format(count, g[0], g[1]))
def main():
goods = '书包'
depth = 3
start_url = 'https://s.taobao.com/search?q=' + goods
infoList = []
for i in range(depth):
try:
url = start_url + '&s=' + str(44*i)
html = getHTMLText(url)
parsePage(infoList, html)
except:
continue
printGoodsList(infoList)
main()
第一段:常规操作(在上篇文章中已有介绍)
第二段:会发现有一系列乱七八糟的符号串,这是正则表达式-先学习一下正则
正则表达式是为了简洁的表达一组字符串,也可以说正则表达式是通用的字符串表达框架
举例:“PY”,“PYY”,“PYYY”,“PY....”=“PY+”:“+”表示P后面有无穷多个Y
正则表达在文本处理中比较常用
- 如匹配字符串的全部或部分
- 同时查找或替换一组字符串
- 表达文本类型的特征
如下常用表达式
.
操作符 | 说明 | 实例 |
. | 表示任何单个字符 | |
[] | 字符集,对单个字符给出取值范围 | [abc]表示a、b、c,[a-z]表示a到z单个字符 |
[^] | 非字符集,对单个字符给出排除范围 | [^abc]表示非a且非b非c的单个字符 |
* | 前一个字符0次或无限次扩展 | abc*表示ab、abc、abcc |
+ | 前一个字符1次或无限次扩展 | abc+表示abc,abccc |
? | 前一个字符0次或1扩展 | abc?表示abc,ab |
! | 左右表达式任意一个 | abc|def表示abc、def |
操作符 | 说明 | 实例 |
{m} | 扩展前一个字符m次 | ab{2}c表示abbc |
{m,n} | 扩展前一个字符m至n次 | ab{1,2}c表示abc、abbc、 |
^ | 匹配字符串开头 | ^abc表示abc且在一个字符串的开头 |
$ | 匹配字符串的结尾 | abc$表示abc且在一个字符串的结尾 |
() | 分组标记,内部只能使用|操作符 | (abc)表示abc,(abc|def)表示abc、def |
\d | 数字,等价于[0-9] | |
\w | 单词字符,等价于[A-Za-z0-9] | |
、
按照课程先尝试一下
正则表达式 | 对应字符串 |
P(Y|YT|YTH|YTHO)?N | 'PN'、'PYN'、PYTN...... |
PYTHON+ | 'PYTHOM'、'PYTHONN'..... |
PY[TH]ON | 'PYTON','PYTHON'........ |
PY[^TH]?ON | 'PYON','PYAON'....... |
经典正则表达式实例 | 对应字符串 |
^[A-Za-z]+$ | 由26个字母组成的字符串 |
^[A-Za-z0-9]+$ | 由26个字母和数字组成的字符串 |
^-?\d+$ | 整数形式的字符串 |
^[0-9]*[1-9][0-9]*$ | 正整数形式的字符串 |
[1-9]\d{5} | 中国境内邮政编码,6位 |
[\u4e00-\u9fa5] | 匹配中文字符(以utf-8为基础) |
\d{3}-\d{8}|\{4}-\d{7} | 国内电话号码 |
\d+.\d+.\d+.\d+(不精确) | IP地址字符串形式的正则表达式 |
\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}(不精确) | |
| |
正则表达式的表示类型有
- raw string 类型(原生字符串类型)
- string 类型,更繁琐一些
两个类型的区别还是很有意思的,简单介绍下,在string中"\"用作转义符,而在正则表达式中对于有特殊意义的字符前面也要加上"\"才能匹配,如想要匹配"*"就需要"\*",这样区别就很明显了,string类型比raw string类型还要多了一次转义,比如:要匹配"\c"用string类型是这样re.search('\\\\c',text)而用原生字符串表示的话就可以这样re.search("\\c",text),为什么呢???
注意string比raw string多了一次转义,所以'\\\\c'先被string转义为'\\c'然后又在正则中转义为"\c"大致就是这样的一个过程
re库的介绍
import re
导入
re库的主要功能函数
函数 | 说明 | |||||||
re.search(pattern,string,flags=0) | 在一个字符串中搜索匹配正则表达式的第一个位置,返回match对象 | |||||||
re.match(pattern,string ,flags=0) | 从一个字符串的开始位置起匹配正则表达式,返回match对象 | |||||||
re.findall(pattern,string,flags=0) | 搜索字符串,以列表类型返回全部能匹配的字串 | |||||||
re.split(pattern,string,maxsplit=0,flags=0) | 将一个字符串按照正则表达式匹配结果进行分割,返回列表类型 | |||||||
re.finditer() | 搜索字符串,返回一个匹配结果的迭代类型,每个迭代元素是match对象 | |||||||
re.sub(pattern,repl,string,count=0,flags=0) | 在一个字符串中替换所有匹配正则表达式的字串,返回替换后的字符串 | |||||||
re.compile(pattern,flags=0) | 将正则字符串编译成正则表达式对象 | |||||||
pattern | 正则表达式的字符串 | string | 要与正则表达式匹配的字符串 | flag(正则表达式的控制标记) | re.I 忽略正则表达式的大小写 | | ||
re.M ‘^’操作符能将给定字符串的每行当做匹配开始 | ||||||||
re.S '.'能够匹配处换行外的所有字符 |
表格中提到返回match对象,那么这个match对象是什么呢???
Match的属性
属性 | 说明 |
.string | 待匹配的文本 |
.re | 匹配时使用的pattern对象(正则表达式) |
.pos | 正则表达式搜索文本的开始位置 |
.endpos | 正则表达式搜索文本的结束位置 |
Match的方法
方法 | 说明 |
.group(0) | 获得匹配后的字符串 |
.start() | 匹配字符串在原始字符串的开始位置 |
.end() | 匹配字符串在原始字符串的结束位置 |
.span() | 返回(.star(),.end()) |
re库的俩种等价用法
- 一次性操作:rst=re.search(r'[1-9]\d{5}','BIT 100081')
- 编译后多次操作:pat=re.compile(r'[1-9]\d{5}') rst=pat.search('BIT 100081')
问题又来了,假设现在要匹配的字符串是这样的 re.search(r'PY.*N','PYANBNCNDN')
这个时候函数该返回什么呢???事实上re库采用的是贪婪匹配就是匹配的尽量多输出最长的字串,所以返回的是PYANBNCNDN
那么有时候也会想要最短的字符串此时如何改进???
操作符 | 说明 |
*? | 前一个字符0次或无限次扩展,最小匹配 |
+? | 前一个字符1次或无限次扩展,最小匹配 |
?? | 前一个字符0次或一次扩展,最小匹配 |
{m,n}? | 扩展前一个字符m至n次,最小匹配 |
掌握了上面的知识,来看一下这第二段函数究竟想表达什么
def parsePage(ilt, html):
try:
plt = re.findall(r'\"view_price\"\:\"[\d\.]*\"',html)
tlt = re.findall(r'\"raw_title\"\:\".*?\"',html)
for i in range(len(plt)):
price = eval(plt[i].split(':')[1])
title = eval(tlt[i].split(':')[1])
ilt.append([price , title])
except:
print("")
plt = re.findall(r'\"view_price\"\:\"[\d\.]*\"',html)这句话,传了一个原生字符串 r'\"view_price\"\:\"[\d\.]*\"'这个要匹配什么呢?其实通过淘宝商品网页的分析发现HTML页面的价格标签为"view_price":"158.00"
这个表达式就是为了可以匹配到这个内容
tlt = re.findall(r'\"raw_title\"\:\".*?\"',html):同理这个是为了可以匹配到商品的名字
for i in range(len(plt)):
price = eval(plt[i].split(':')[1])
title = eval(tlt[i].split(':')[1])
ilt.append([price , title])
except:
print("")
这里的len用于返回对象的长度,我感觉应该是字串的个数。eval函数在这里的作用主要还是用于去掉双引号,eval函数在这里主要是用来去掉双引号,eval函数的其他用途是什么呢?请看这篇
eval函数的作用
剩下的函数便不用在解释了,所以这个函数便是从返回的HTML页面找到名称和价格并返回列表
最后一个函数:就不介绍了,有些简单。。。
二:获取股票相关信息的代码,之后会一一解释作用
#CrawBaiduStocksB.py
import requests
from bs4 import BeautifulSoup
import traceback
import re
def getHTMLText(url, code="utf-8"):
try:
r = requests.get(url)
r.raise_for_status()
r.encoding = code
return r.text
except:
return ""
def getStockList(lst, stockURL):
html = getHTMLText(stockURL, "GB2312")
soup = BeautifulSoup(html, 'html.parser')
a = soup.find_all('a')
for i in a:
try:
href = i.attrs['href']
lst.append(re.findall(r"[s][hz]\d{6}", href)[0])
except:
continue
def getStockInfo(lst, stockURL, fpath):
count = 0
for stock in lst:
url = stockURL + stock + ".html"
html = getHTMLText(url)
try:
if html=="":
continue
infoDict = {}
soup = BeautifulSoup(html, 'html.parser')
stockInfo = soup.find('div',attrs={'class':'stock-bets'})
name = stockInfo.find_all(attrs={'class':'bets-name'})[0]
infoDict.update({'股票名称': name.text.split()[0]})
keyList = stockInfo.find_all('dt')
valueList = stockInfo.find_all('dd')
for i in range(len(keyList)):
key = keyList[i].text
val = valueList[i].text
infoDict[key] = val
with open(fpath, 'a', encoding='utf-8') as f:
f.write( str(infoDict) + '\n' )
count = count + 1
print("\r当前进度: {:.2f}%".format(count*100/len(lst)),end="")
except:
count = count + 1
print("\r当前进度: {:.2f}%".format(count*100/len(lst)),end="")
continue
def main():
stock_list_url = 'https://quote.eastmoney.com/stocklist.html'
stock_info_url = 'https://gupiao.baidu.com/stock/'
output_file = 'D:/BaiduStockInfo.txt'
slist=[]
getStockList(slist, stock_list_url)
getStockInfo(slist, stock_info_url, output_file)
main()
由于百度股票已经凉了,所以。。。。。。。。。。这个代码实践意义有些过小,不过没关系,它还有学习意义呀。。。。
这里最重要的还是第二个函数 def getStockList(lst, stockURL):
def getStockInfo(lst, stockURL, fpath):
count = 0
for stock in lst:
url = stockURL + stock + ".html"
html = getHTMLText(url)
try:
if html=="":
continue
infoDict = {}
soup = BeautifulSoup(html, 'html.parser')
stockInfo = soup.find('div',attrs={'class':'stock-bets'})
name = stockInfo.find_all(attrs={'class':'bets-name'})[0]
infoDict.update({'股票名称': name.text.split()[0]})
keyList = stockInfo.find_all('dt')
valueList = stockInfo.find_all('dd')
for i in range(len(keyList)):
key = keyList[i].text
val = valueList[i].text
infoDict[key] = val
with open(fpath, 'a', encoding='utf-8') as f:
f.write( str(infoDict) + '\n' )
count = count + 1
print("\r当前进度: {:.2f}%".format(count*100/len(lst)),end="")
except:
count = count + 1
print("\r当前进度: {:.2f}%".format(count*100/len(lst)),end="")
continue
关于BeautifulSoup类上一篇已讲过小白python爬虫学习2(初识Request,Beautifulsoup,print)在此不再赘述
关于split()[0]-----split函数默认为所有的空字符,包括空格、换行(\n)、制表符(\t)等取分割的第一个
关于不换行的进度条 关键"\r"将光标提到第一行首位。。。。。。
OK终于写完了,以后再补充