宗旨:网站即是API
- 准备工作
1. robots 协议
爬取网站一定要遵守robots.txt,否则会有法律风险(面向监狱编程)。在网站的域名后加 /robots.txt 可以看到其对爬虫的限制。比如: 百度:https://www.baidu.com/robots.txt B站:https://www.bilibili.com/robots.txt 帝国理工:http://www.imperial.ac.uk/robots.txt
2. 安装python环境——Anaconda傻瓜式安装
3. 安装库文件:pip install requests 装完后运行以下代码测试:
import requests
r = requests.get("http://www.baidu.com/")
r.raise_for_status()
print(r.status_code) #HTTP 状态码
r.encoding = "utf-8"
print(r.text) #打印网站内容
4. 安装HTML解析库:pip install beautifulsoup4 装完后运行以下代码测试:
import requests
from bs4 import BeautifulSoup
url = "https://python123.io/ws/demo.html"
pa = {'User-Agent': 'Mozilla/5.0'}
r = requests.get(url, headers = pa)
r.raise_for_status()
r.encoding = r.apparent_encoding
demo = r.text
soup = BeautifulSoup(demo,"html.parser")
print(soup.prettify())
5. 安装 scrapy 框架:pip install scrapy
若报超时错误,更换为国内源即可,代码如下:pip install scrapy -i [http://pypi.douban.com/simple](http://pypi.douban.com/simple) --trusted-host pypi.douban.com
安装完成后,在命令行运行scrapy -h 测试。
6. 了解生成器——yield
def gen(n):
for i in range(n):
yield i**2
for i in gen(6):
print(i,end = " ")
#[out]: 0 1 4 9 16 25
生成器的优势:
- 更节省储存空间
- 响应更迅速
- 使用更灵活
- requests库
0. requests库的7个主要方法
除去第一个,其余的与HTTP请求相对应
- requests.request():构造一个请求,支撑以下各方法的基础方法
- requests.get():获取HTML网页的主要方法
- requests.head():获取HTML网页的头部信息
- requests.post():向HTML网页提交POST请求
- requests.put():向HTML网页提交PUT请求
- requests.patch():向HTML网页提交局部修改请求
- requests.delete():向HTML网页提交删除请求
1. requests.get(“url”)
r = requests.get("url")
返回Response对像(即上面代码中的r)的属性:
r.status_code:HTTP请求的返回状态;详情点这里r.text:HTTP响应内容的字符串形式——url对应的页面内容r.encoding:响应内容的编码方式(header中分析出)r.apparent_enconding:备选编码方式(内容中分析出)r.content:响应内容的二进制形式
当 r.encoding 不能正确编码时,尝试使用 r.apparent_enconding 的编码方式
爬取网页的通用代码框架:
import requests
def getHTMLText(url):
try:
r = requests.get(url)
r.raise_for_status()
r.encoding = r.apparent_encoding
return r.text
except:
return "ERROR--getHTMLText"
if __name__ == "__main__":
url = "http://www.baidu.com/"
print(getHTMLText(url))
- requests实战
1. 爬取京东商品页面
要修改HTTP请求的headers信息,变成浏览器访问。可以把以下代码放进一个函数,方便使用
import requests
url = "https://item.jd.com/100008771796.html"
kv = {'User-Agent': 'Mozilla/5.0'}
try:
r = requests.get(url, headers = kv)
r.raise_for_status()
r.encoding = r.apparent_encoding
print(r.text[:1000])
except:
print(r.request.headers)
print(r.status_code)
print("ERROR--getHTMLText")
2. 百度搜索关键词提交
import requests
url = "http://www.baidu.com/s"
pa = {'User-Agent': 'Mozilla/5.0'}
kv = {'wd': 'python'}
try:
r = requests.get(url, headers = pa, params = kv)
r.raise_for_status()
r.encoding = r.apparent_encoding
print(r.text[:1500])
print(r.request.headers)
except:
print(r.request.headers)
print(r.status_code)
print("ERROR--getHTMLText")
- 网络图片的爬取和储存(也可以是视频和gif)
import requests
url = "IMAGE_URL"
path = "C:/Users/Ran/Desktop/abc.jpg" #文件储存路径+文件名+文件格式
r = requests.get(url)
print(r.status_code)
with open(path,'wb') as f:
f.write(r.content)
- IP归属地查询
查询IP地址的网站的url会时常更新,以下代码可能失效。若不成功,更换最新的url即可。
import requests
url = "https://www.ip138.com/iplookup.asp?ip="
ip = "117.157.165.230"
url += ip
url += "&action=2"
pa = {'User-Agent': 'Mozilla/5.0'}
r = requests.get(url, headers = pa)
print(r.status_code)
r.encoding = r.apparent_encoding
print(r.text)
- BeautifulSoup 库
1. 有4种解析器,最常用的是 ‘html.parser’。其他三种需要安装对应的库
在运行本小节其他代码前,请先运行以下代码,然后复制其他代码到命令窗口运行,或者将其他代码复制到这段代码下运行整个程序。
import requests
from bs4 import BeautifulSoup
url = "https://python123.io/ws/demo.html"
pa = {'User-Agent': 'Mozilla/5.0'}
r = requests.get(url, headers = pa)
r.raise_for_status()
r.encoding = r.apparent_encoding
demo = r.text
soup = BeautifulSoup(demo,"html.parser")
2. 5种基本元素
- Tag:标签——最基本的信息组织单位;以<>开头,以</>结尾
- Name:标签的名字;
…
的名字是‘p’,格式:.name - Attributes:标签的属性;以字典形式组织,格式:
.attrs - NavigableString:标签内非字符串的属性;<>…</>中的字符,格式:
.string - Comment:标签内字符串的注释部分,一种特殊的comment类型
soup.title
Out[33]: <title>This is a python demo page</title>
tag = soup.a
print(tag)
<a class="py1" href="http://www.icourse163.org/course/BIT-268001" id="link1">Basic Python</a>
soup.a.name
Out[36]: 'a'
soup.a.parent.name
Out[37]: 'p'
soup.a.parent.parent.name
Out[38]: 'body'
soup.a.parent.parent.parent.name
Out[39]: 'html'
tag.attrs
Out[40]:
{'href': 'http://www.icourse163.org/course/BIT-268001',
'class': ['py1'],
'id': 'link1'}
tag.attrs['id']
Out[41]: 'link1'
type(tag)
Out[42]: bs4.element.Tag
tag.string
Out[43]: 'Basic Python'
soup.p.string
Out[44]: 'The demo python introduces several python courses.'
3. HTML文件遍历方法
标签树的下行遍历
- .contents:返回子节点的列表,将
所有子节点存入列表 - .children:子节点的迭代类型,与 .contents 类似,用于循环编译子节点
- .descendants:子孙节点的迭代类型,包含所有子孙节点,用于循环遍历
for child in soup.body.children:
print(child)
标签树的上行遍历
- .parent:节点的父亲标签
- .parents:节点先辈标签的迭代类型,用于循环遍历先辈节点
for parent in soup.a.parents:
if parent is None:
print(parent)
else:
print(parent.name)
标签书的平行遍历
- .next_sibling:返回下一个平行节点的标签
- .previous_sibling:返回上一个平行节点的标签
- .next_siblings:迭代类型,返回之后所有的平行节点的标签
- .previous_siblings:迭代类型,返回之前所有的平行节点的标签
平行遍历必须在同一父节点下的各节点之间
soup.a.next_sibling
Out[56]: ' and '
soup.a.next_sibling.next_sibling
Out[57]: <a class="py2" href="http://www.icourse163.org/course/BIT-1001870001" id="link2">Advanced Python</a>
soup.a.previous_sibling
Out[58]: 'Python is a wonderful general-purpose programming language. You can learn Python from novice to professional by tracking the following courses:\r\n'
for sibling in soup.a.next_siblings:
print(sibling.name)
None
a
None
4. 让 HTML 的内容更友好的显示
.prettify()
print(soup.a.prettify())
<a class="py1" href="http://www.icourse163.org/course/BIT-268001" id="link1">
Basic Python
</a>
5. 信息提取的一般方法
<>.find_all(name, attrs, recursive, string, **kwargs)——以列表的形式返回查找的结果
- name:对标签名称的检索字符串
- attrs:对标签属性值的检索字符串
- recursive:是否对子孙全部检索(默认为True)
- string:<>…</>中字符串区域的检索字符串
以下代码在console中运行
import re #正则表达式库
for tag in soup.find_all(re.compile('b')):
print(tag.name)
Out[4]:
body
b
soup.find_all(id=re.compile('link'))
Out[5]:
[<a class="py1" href="http://www.icourse163.org/course/BIT-268001" id="link1">Basic Python</a>,
<a class="py2" href="http://www.icourse163.org/course/BIT-1001870001" id="link2">Advanced Python</a>]
soup.find_all(string = re.compile('python'))
Out[6]:
['This is a python demo page',
'The demo python introduces several python courses.']
简写/等价
(…) == .find_all(…) - soup.(…) == soup.find_all(…)
.find() 还有7中扩展用法,这里就不一一列出了。
- 第二阶段的实战
1.世界大学排名的定向爬虫
import requests
import bs4
def getHTMLText(url):
try:
kv = {'User-Agent': 'Mozilla/5.0'}
r = requests.get(url, headers = kv, timeout = 30)
r.raise_for_status()
r.encoding = r.apparent_encoding
return(r.text)
except:
return("")
def fillUnivList(uList, html):
soup = BeautifulSoup(html, 'html.parser')
for tr in soup.find('tbody').children:
if isinstance(tr, bs4.element.Tag):
tds = tr('td')
uList.append([tds[0].string, tds[1].string, tds[4].string])
def printUnivList(uList, num):
template = "{0:^10}\t{1:{3}^10}\t\t{2:^10}"
print(template.format("排名","学校","总分",chr(12288)))
for i in range(num):
u = uList[i]
print(template.format(u[0], u[1], u[2], chr(12288)))
if __name__ == "__main__":
uinfo = []
url = "http://www.zuihaodaxue.com/ARWU2019.html"
html = getHTMLText(url)
fillUnivList(uinfo, html)
printUnivList(uinfo, 20)
运行后输出:
runfile('D:/Anaconda Spyder/crawler.py', wdir='D:/Anaconda Spyder')
排名 学校 总分
1 哈佛大学 100.0
2 斯坦福大学 75.1
3 剑桥大学 72.3
4 麻省理工学院 69.0
5 加州大学-伯克利 67.9
6 普林斯顿大学 60.0
7 牛津大学 59.7
8 哥伦比亚大学 59.1
9 加州理工学院 58.6
10 芝加哥大学 55.1
11 加州大学-洛杉矶 50.8
11 耶鲁大学 50.8
13 康奈尔大学 49.8
14 华盛顿大学-西雅图 48.7
15 伦敦大学学院 47.9
16 约翰霍普金斯大学 47.6
17 宾夕法尼亚大学 47.3
18 加州大学-圣地亚哥 47.1
19 苏黎世联邦理工学院 46.1
20 加州大学-旧金山 42.2
- re库
正则表达式是用来简洁表达一组字符串的,比如以下字符:
“PN”, “PYN”, “PYTN”, “PYTHN”, “PYTHON”
可以用正则表达式:P(Y|YT|YTH|YTHO)?N 表示
1.正则表达式的语法
正则表达式是由字符和操作符组成的
常用操作符:
- . ——表示任何单个字符
- [] ——字符集,对单个字符给出取值范围
- [^] ——非字符集,对单个字符给出排除范围
-
- ——前一个字符0次或者无限次扩展
-
- ——前一个字符1次或者无限次扩展
- ? ——前一个字符0次或者1次次扩展
- | ——左右表达式任意一个
- {m} ——扩展前一个字符m次
- {m, n} ——扩展前一个字符m至n次(包括n)
- ^ ——匹配字符串开头
- $ ——匹配字符串结尾
- () ——分组标记,内部只能使用 | 操作符
- \d ——数字,等价于[0-9]
- \w ——单词字符,等价于[A-Za-z0-9_]
raw string——原生字符串类型, ’' 转义符不起作用。若包含转义符,正则表达式最好使用 raw string 类型
2. re库主要的功能函数
- re.search():搜索匹配正则表示式的第一个位置,返回match对象
- re.match():从开始位置起匹配正则表示式,返回match对象
- re.findall():以列表形式返回所有能匹配的字符串
- re.split():将正则表达式的结果进行分割,以列表形式返回
- re.finditer():返回匹配结果的迭代类型,每个迭代元素都是match对象
- re.sub():替换所有匹配正则表达式的字串,返回替换后的字符串
3. 正则表达式对象
re.compile(pattern, flag=0)——将正则表达式的字符串形式编译为正则表达式对象
regex = re.compile(pattern, flag=0)
4. match对象的属性
- .string:待匹配的文本
- .re:匹配时使用的pattern对象,正则表达式
- .pos:正则表达式搜索文本的开始位置
- .endpos:正则表达式搜索文本的结束位置
5. match对象常用的方法
- .group(0):获得匹配后的字符串
- .start():匹配字符串在原始字符串的开始位置
- .end():匹配字符串在原始字符串的结束位置
- .span():返回(.start(), .end())
6. re库的贪婪匹配和最短匹配
re库默认采用贪婪匹配,即输出匹配最长的字串
import re
match = re.search(r'PY.*N', 'PYANSICJHN')
print(match.group(0)) #out: PYANSICJHN
若想输出最短字串,要加一个 ‘?’
import re
match = re.search(r'PY.*?N', 'PYANSICJHN')
print(match.group(0)) #out: PYAN
-
第三阶段实战
-
淘宝信息定向爬取
需要填写header,否则会跳到登陆验证页面。头部的获取方法点这里 或者看下一段括号内的方法,获得后把内容填到第6行的 kv 中。
这里要感谢B站大佬 浪精灵 给出的方法 (首先登陆你的淘宝账号,然后按 F12 进入检查,在上面的 network 的第一行中拖到最后,有一个Request Headers,右键 Copy–>Copy as URL(bash),然后打开这个网站(点这里)。 在里面把复制的内容放进去,选择 Python 然后会发现右边有一个 headers:{省略…},把这个 headers 放进代码,用一个变量储存,然后在代码中的r equest.get(url,headers=你刚才复制的内容,也就是那个变量),这个时候你再请求 request.text 返回的就是你要的页面)
import re
import requests
def getHTMLText(url):
try:
kv = {} #获取方法请见上面
r = requests.get(url, headers = kv, timeout = 30)
r.raise_for_status()
r.encoding = r.apparent_encoding
#print(r.text)
return(r.text)
except:
return("")
def parsePage(ilt, hyml):
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):
template= "{:4}\t{:8}\t{:16}"
print(template.format("序号","价格", "名称"))
count = 0
for g in ilt:
count += 1
print(template.format(count, g[0], g[1]))
if __name__ == "__main__":
goods = 'switch'
depth = 2
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)
输出结果(这截取了前20行):
In [32]: runfile('D:/Anaconda Spyder/crawler.py', wdir='D:/Anaconda Spyder')
序号 价格 名称
1 2099.00 Nintendo Switch 任天堂家用游戏机续航版增强版 掌机NS体感游戏机 国行Switch游戏机
2 320.00 Switch NS游戏 异度之刃 异度神剑决定终极决战版 十一区中文订购
3 1410.00 任天堂switch续航版ns日版 港版 国行ns主机lite掌机动物之森限定
4 2099.00 Nintendo Switch 任天堂家用游戏机续航版增强版 掌机NS体感游戏机 国行Switch游戏机
5 3499.00 任天堂NS Switch续航主机 动物之森限定版 动森限定 现货电玩巴士
6 1449.00 电玩巴士 任天堂Switch续航加强 NS国行日版Lite游戏主机 掌机
7 1449.00 创想任天堂Switch主机Lite游戏机NS续航版国行日版动物之森限定
8 2049.00 现货任天堂 NS switch动物之森 森友会 限定版主机 游戏机 港日版
9 2049.00 创想电玩任天堂 Switch新版游戏主机NS续航国行港日版限定 健身环
10 1409.00 任天堂Switch主机Lite游戏机续航版NS国行港日版动物之森限定版
11 1419.00 任天堂switch Lite掌机NS游戏机 动物森友会限定机 续航版 日版
12 999.00 抖音XBOX360ES体感游戏机switch电玩PS双人电视体感wiiu家用4人on
13 1419.00 国行 任天堂switch lite游戏机NS主机续航加强版动物之森限定日版
14 1439.00 任天堂Switch NS主机 Lite游戏掌机 续航加强版 动物之森日版国行
15 978.00 现货 NS游戏 健身环大冒险NS Ring fit Adventure 中文 switch
16 1408.00 任天堂switch NS主机 Lite掌机 续航加强版主机 宝可梦 剑盾 粉色
17 886.00 任天堂Switch游戏 NS健身环大冒险 Ring fit Adventure 现货即发
18 1419.00 任天堂switch主机lite游戏机NS续航加强版动物之森限定港日版国行
19 1409.00 任天堂switch lite游戏机NS主机续航加强版动物之森限定机港日版
20 2049.00 北京华冕 任天堂Switch NS主机掌机游戏机 新续航 国行/日港版
- scrapy 框架
具体信息点这里
1.requests与scrapy对比
| requests | scrapy |
|---|---|
| 页面级爬虫 | 网站级爬虫 |
| 功能库 | 框架 |
| 并发性考虑不足,性能较差 | 并发性好,新能较高 |
| 重点在于页面下载 | 重点在于爬虫结构 |
| 定制灵活 | 深度定制困难 |
2. scrapy命令行格式
sctapy<command>[options][args]
3. scrapy常用命令
- startproject:创建一个新工程
- genspider:创建一个爬虫
- settings:获取爬虫配置信息
- crawl:运行一个爬虫
- list:列出工程中的所有爬虫
- shell:启动url调试命令行
- scrapy 使用步骤
1.建立一个scrapy爬虫工程
(base) C:\Users\Ran>scrapy startproject python123demo
New Scrapy project 'python123demo', using template directory 'd:\anaconda
\lib\site-packages\scrapy\templates\project', created in:
C:\Users\Ran\python123demo
You can start your first spider with:
cd python123demo
scrapy genspider example example.com
2. 在工程中产生一个scrapy爬虫
(base) C:\Users\Ran>cd python123demo
(base) C:\Users\Ran\python123demo>scrapy genspider demo python123.io
Created spider 'demo' using template 'basic' in module:
python123demo.spiders.demo
3. 配置产生的spider
修改新创建的文件夹(地址如第一步命令行所示)下的spider文件夹下的 新的p文件(py文件名如第二部第二行所示(genspider的下一个单词))。代码如下:
# -*- coding: utf-8 -*-
import scrapy
class DemoSpider(scrapy.Spider):
name = "demo"
#allowed_domains = ['python123.io']
start_urls = ['http://python123.io/wa/demo.html']
def parse(self, response):
fname = response.url.split('/')[-1]
with open(fname,'wb') as f:
f.write(response.body)
self.log('Saved file %s.' % fname)
- 运行爬虫以获取页面
(base) C:\Users\Ran\python123demo>scrapy crawl demo
运行后可以看到,在‘python123demo’目录下生成了 demo.html 文件