实用Python程序设计MOOC-第十二章网络爬虫设计

实用Python程序设计MOOC-第十二章网络爬虫设计

[TOC]

实用Python程序设计MOOC-第十二章网络爬虫设计

爬虫基本原理

爬虫的用途和原理

1) 在网络上搜集数据(比如搜索引擎)
2) 模拟浏览器快速操作(抢票,抢课,抢挂号…..)
3) 模拟浏览器操作,替代填表等重复操作

最基本的爬虫写法

数据获取型爬虫的本质就是自动获取网页并抽取其中的内容
1) 手工找出合适的url(网址) 。
2) 用浏览器手工查看url对应的网页,并查看网页源码,找出包含想要的内容(文件名,链接等)的字符串的模式。
3) 程序中获取url对应的网页。
4) 程序中用正则表达式或BeautifulSoup库抽取网页中想要的内容并保存。

示例:获取百度图片的搜索结果图片

  1. /在百度图片敲关键字“desk”进行搜索
  2. 搜索后看浏览器地址栏的地址:
    搜索后看浏览器地址栏的地址

  3. 猜测只要在浏览器输入下面地址的word=内容,替换其中的单词,就能搜到图片:

1
2
3
https://image.baidu.com/search/index?tn=baiduimage&ipn=r&ct=201326592&cl=2&lm=-1&st=-1&fm=index&fr=&hs=0&xthttps=111111&sf=1&fmq=&pv=&ic=0&nc=1&z=&se=1&showtab=0&fb=0&width=&height=&face=0&istype=2&ie=utf-8&word=desk

https://image.baidu.com/search/index?tn=baiduimage&ipn=r&ct=201326592&cl=2&lm=-1&st=-1&fm=index&fr=&hs=0&xthttps=111111&sf=1&fmq=&pv=&ic=0&nc=1&z=&se=1&showtab=0&fb=0&width=&height=&face=0&istype=2&ie=utf-8&word=猫

浏览器输入后出现猫的搜索结果

  1. 用浏览器访问刚才的url,并用浏览器查看源码(chrome)

  2. 在源码中查找

复制出来的图片地址:https://img1.baidu.com/it/u=716463119,473541077&fm=26&fmt=auto&gp=0.jpg
为某张图片的网络地址,此链接是百度保存的缩略图的网址

查看网页源代码后搜索图片连接,找到源码:
716463119,473541077&fm=26&fmt=auto&gp=0.jpg

发现图片链接在网页里都是这样的:
{"thumbURL":"https://img1.baidu.com/it/u=716463119,473541077&fm=26&fmt=auto&gp=0.sjpg", "adType":"0","midd

即thumbURL后跟的是图片连接。

可以用正则表达式提取图片链接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import re
import requests #request库用于获取网络资源 pip install requests
def getHtml(url): #获取网址为url的网页
#具体实现略,后面再讲述
def getBaiduPictures(word,n):
#下载n个百度图片搜来的关于word的图片保存到本地
url="https://image.baidu.com/search/index?tn=baiduimage&ipn=r&ct=201326592&cl=2&lm=-1&st=-1&fm=index&fr=&hs=0&xthttps=111111&sf=1&fmq=&pv=&ic=0&nc=1&z=&se=1&showtab=0&fb=0&width=&height=&face=0&istype=2&ie=utf-8&word="
url += word
html = getHtml(url)
pt = '\"thumbURL\":.*?\"(.*?)\"' #正则表达式,用于寻找图片网址
i = 0
#"thumbURL":"https://img1.baidu.com/it/u=716463119,473541077&fm=26&fmt=auto&gp=0.jpg",
for x in re.findall(pt, html): #x就是图片url
print(x)
x = x.lower()
try:
r = requests.get(x, stream=True)#获取x对应的网络资源
f = open('{0}{1}.jpg'.format(word,i), "wb")
#"wb"表示二进制写方式打开文件
f.write(r.content) #图片内容写入文件
f.close()
i = i + 1
except Exception as e :
pass
if i >= n:
break
getBaiduPictures("猫", 5)
getBaiduPictures("熊猫", 5)

用requests库获取网页

用requests.get获取网页

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def getHtml(url):  # 获取网址url的网页
import requests # request库用于获取网络资源,pip install request
fakeHeaders = {'User-Agent': # 用于伪装浏览器发送请求
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) \
AppleWebKit/537.36 (KHTML, like Gecko) \ '
'Chrome/81.0.4044.138 Safari/537.36 Edg/81.0.416.77',
'Accept': 'text/html,application/xhtml+xml,*/*'}
try:
r = requests.get(url, headers=fakeHeaders)

r.encoding = r.apparent_encoding # 确保网页编码正确

return r.text # 返回值是个字符串,内含整个网页内容
except Exception as e:
print(e)
return ""
# 用法: html = getHtml("http://openjudge.cn")

用requests.get获取网页(编码识别加强版)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def getHtml(url):
import sys, requests
import chardet # 编码处理库 pip install chardet
fakeHeaders = {'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) \
AppleWebKit/537.36 (KHTML, like Gecko) \ '
'Chrome/81.0.4044.138 Safari/537.36 Edg/81.0.416.77',
'Accept': 'text/html,application/xhtml+xml,*/*'}
try:
r = requests.get(url, headers=fakeHeaders)

ecd = chardet.detect(r.content)['encoding'] # ecd是个字符串
if ecd.lower() != sys.getdefaultencoding().lower():
r.encoding = ecd # 修改r中文本的编码
else:
r.encoding = r.apparent_encoding

return r.textv
except Exception as e:
print(e)
return ""

用requests库获取网页的优势和局限

  • 优势
  • 相比其它方法, 速度快几倍
  • 安装简单,分发容易
  • 局限
  • 容易被反爬虫手段屏蔽
  • 不能获取包含javascript生成的动态网页
    用上面的getHtml函数,不能得到百度图片搜索结果网页,得到的网页是空网页(被反爬了)

弥补requests不足的其它获取网页的办法

  • 使用selenium库 (慢,很容易被反爬,且已经被许多网站反爬,网上各种对付反爬的解决办法基本不管用,不推荐)
  • 使用pyppeteer库(快,暂未被许多网站反爬,强烈推荐)

用selenium库获取网页

pip install selunium

需要chrome浏览器或firefox浏览器,此外还需要下载chrome驱动程序(chromedriver.exe)或firefox驱动程序(geckodriver.exe)

用selenium获取网页

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def getHtml(url):  # 暂时适用于百度图片搜索
from selenium import webdriver # 需要pip install selenium
from selenium.webdriver.chrome.options import Options
options = Options() # 浏览器选项
# 等价于 options = webdriver.chrome.options.Options()
options.add_argument('--headless') # 规定chrome浏览器隐身模式运行
options.add_argument('--disable-gpu') # 禁止chrome使用gpu加速,能快点
driver = webdriver.Chrome(
executable_path='c:/tmp/chromedriver.exe', options=options)
# driver就是个chrome浏览器。需要下载安装chrome驱动器 chromedriver.exe
driver.get(url) # 浏览器装入网页
html = driver.page_source # 网页源代码
driver.close() # 关闭浏览器
driver.quit() # 退出
return html # 返回字符串

用pyppeteer库获取网页

  • puppeteer是谷歌公司推出的可以控制Chrome浏览器的一套编程工具。一个日本工程师以此为基础推出了Python版本,叫pyppeteer。

  • pyppeteer的官网

  • 启动一个浏览器Chromium,用浏览器装入网页。浏览器可以用无头模式(headless),即隐藏模式启动,也可以显式启动。

  • 从浏览器可以获取网页源代码,若网页有javascript程序,获取到的是javascript被浏览器执行后的网页源代码。

  • 可以向浏览器发送命令,模拟用户在浏览器上键盘输入、鼠标点击等操作,让浏览器转到其它网页。

  • selenium原理及功能和pyppeteer一样。

环境安装

pip install pyppeteer

  • 要求Python版本 >= 3.6
  • 必须下载并安装特殊版本的谷歌浏览器Chromium

可以将Chromium压缩包随便解压在哪个文件夹,然后在程序指明其中chrome.exe的位置。
也可以将Chromium解压到pyppeteer的安装文件夹下面。这个文件夹通常类似:
C:\Users\username\AppData\Local\pyppeteer\pyppeteer\local-chromium\588429
把username要换成自己的windows用户名, 588429这里可能是别的数。
将Chromium压缩包里面的chrome-win32文件夹整个放在上面那个文件夹里面就行

预备知识:协程

  • 协程就是前面加了’async’的函数(从Python 3.6开始有)
1
2
async def f()
return 0
  • 调用协程时,必须在函数名前面加’await’
1
await f()
  • 协程只能在协程里面调用,即await语句只能出现在协程里面。

  • 协程是一种特殊的函数,多个协程可以并行。

  • pyppeteer中的所有函数都是协程,调用时前面都要加 await,且只能在协程中调用
    初用协程,经常因为调用XXXX时忘了加await导致下面错误:

1
RuntimeWarning: coroutine 'XXXX' was never awaited

用pyppeteer获取网页

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def getHtml(url):  # 暂时适用于百度图片搜索
import asyncio # Python 3.6之后自带的协程库
import pyppeteer as pyp
async def asGetHtml(url): # 获取url对应网页的源代码
browser = await pyp.launch(headless=False)
# 启动Chromium,browser即为Chromium浏览器,非隐藏启动
page = await browser.newPage() # 在浏览器中打开一个新页面(标签)

await page.setUserAgent('Mozilla/5.0 (Windows NT 6.1; \
Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) \
Chrome/78.0.3904.70 Safari/537.36') # 反反爬措施
await page.evaluateOnNewDocument(
'() =>{ Object.defineProperties(navigator, \
{ webdriver:{ get: () => false } }) }') # 反反爬措施

await page.goto(url) # 装入url对应的网页
text = await page.content() # page.coutent就是网页源代码字符串
await browser.close() # 关闭浏览器
return text
# 速度大约比用requests.get慢5,6倍

m = asyncio.ensure_future(asGetHtml(url)) # 协程外启动协程
asyncio.get_event_loop().run_until_complete(m) # 等待协程结束
return m.result() # 返回的就是asGetHtml的返回值 text

可以改进程序,只需要启动一次浏览器,生成一个page对象即可,以后获取不同网页都用相同page对象,所有事情完成后才关闭浏览器。

  • launch的其它参数
1
browser = await launch(headless=False, executablePath="c:/tmp/chrome-win32/chrome.exe", userdataDir="c:/tmp")

excutablePath: 如果Chromium没有安装在默认文件夹下面,则需要指定其位置。
userdataDir: userdataDir指明用来存放浏览器工作期间存放临时文件的文件夹。不是必须,能够防止可能出现的莫名其妙的错误。

用BeautifulSoup库分析网页

分析并提取网页内容的三种方式

  1. 正则表达式(速度最快,但适应变化略差)
  2. BeautifulSoup库 (速度是正则表达式的约几分之一)
  3. selenium或pyppeteer的中的浏览器对象的查找元素函数(速度是正则表达式的约百分之一,用在需要模拟在网页中进行输入,点击按钮等操作的时候)

html文档(网页)中的tag

tag格式通常为(少数没有正文和< /X >):

1
2
3
<X attr1='xxx' attr2='yyy' attr3='zzz' …>
nnnnnnnnnnnnnn
</X>
  • X : tag的名字(name)
  • attr1,attr2… : tag的属性(attr) =后面跟着属性的值
  • nnnnnnnnnnnnnn : tag的正文(text)

例如:

1
<a href="www.sohu.com" id='mylink'>搜狐网</a>

  • a :tag的名字(name)
  • href, id : tag的属性(attr), =后面跟着属性的值
  • 搜狐网 : tag的正文(text)

tag可以嵌套

1
2
3
4
5
6
7
<div id="siteHeader" class="wrapper">
<h1 class="logo">
<div id="topsearch">
<ul id="userMenu">
<li ><a href="http://openjudge.cn/">首页</a></li>
</div>
</div>

用BeautifulSoup库分析html

  • 安装
1
pip install beautifulsoup4
  • 导入
1
import bs4
  • 使用

1) 将html文档装入一个BeautifulSoup对象X
2) 用X对象的find,find_all等函数去找想要的tag对象
3) 对找到的tag对象,还可以用其find,find_all函数去,找它内部包含(嵌套)的tag对象
4) tag对象的text就是该对象里的正文(text), tag对象也可以看作是一个字典,里面包含各种属性(attr)及其值。

把html文档载入BeautifulSoup对象

  • 方法1,html文档来自字符串:
1
2
3
4
5
6
7
8
9
10
11
12
13
str = '''
<div id="siteHeader" class="wrapper">
<h1 class="logo">
<div id="topsearch">
<ul id="userMenu">
<li ><a href="http://openjudge.cn/">首页</a></li>
</div>
</div>
'''
#带href的 <a>都是链接,上面“首页”是链接文字 ,href后面http://openjudge.cn是链
接地址
soup = bs4.BeautifulSoup(str, "html.parser")
print(soup.find("li").text) #>>首页
  • 方法2,html文档来自于文件:
1
soup = bs4.BeautifulSoup(open("c:\\tmp\\test.html","r",encoding="utf-8"), "html.parser")
  • 方法3,html文档来自于给定网址:
1
2
3
4
5
6
7
8
9
10
11
12
import requests
def getHtml(url):
#获得html文本
try:
r = requests.get(url)
r.raise_for_status()
r.encoding = r.apparent_encoding
return r.text
except:
return ""
html = getHtml("https://cn.bing.com/dict/search?q=new")
soup = bs4.BeautifulSoup(html,'html.parser')

实例:用BeautifulSoup对象寻找想要的tag

c:\tmp\test.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE HTML>
<html>
<body>
<div id="synoid" style="display:block;">
<div class="df_div2">
<div class="de_title1">adj.</div>
<div class="col_fl">
<a h="ID=Dictionary,5237.1" href="https://cn.bing.com/dict/search?q=novel">
<span class="p1-4">novel</span>
</a>
<p>
<a h="ID=Dictionary,5238.1" href="https://cn.bing.com/dict/search?q=newfangled">
<span class="p1-4">newfangled</span>
</a>
</div>
<a href="http://www.baidu.com" id="searchlink1" class="sh1">百度</a>
<a href="http://www.google.com" id="searchlink1" class="sh2">谷歌</a>
</div>
</div>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import bs4
soup = bs4.BeautifulSoup(open("c:\\tmp\\test.html", encoding = "utf-8"),"html.parser")

diva = soup.find("div",attrs={"id":"synoid"})
#寻找名为"div",且具有值为"synoid"的属性"id"的tag

if diva != None: #如果找到
for x in diva.find_all("span",attrs={"class":"p1-4"}):
print(x.text) #在diva内部继续找
for x in diva.find_all("a",attrs={"id":"searchlink1"}):
print(x.text)
x = diva.find("a",attrs={"id":"searchlink1","class":"sh2"})
if x != None:
print(x.text)
print(x["href"])
print(x["id"])

输出:

1
2
3
4
5
6
7
novel
newfangled
百度
谷歌
谷歌
http://www.google.com
searchlink1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#如果别处没有和 <div id="synoid" style="display:block;"> 内部的模式相似的东西,也可以不用先找这个 "synoid" tag
import bs4
soup = bs4.BeautifulSoup(open("c:\\tmp\\test.html",encoding="utf-8"),"html.parser")

for x in soup.find_all("span",attrs={"class":"p1-4"}):
print(x.text)

for x in soup.find_all("a",attrs={"id":"searchlink1"}):
print(x.text)

x = soup.find("a",attrs={"id":"searchlink1","class":"sh2"})
if x != None:
print(x.text)
print(x["href"])
print(x["id"])

实例:爬取每日股票交易信息

创业板股票交易代码大全
深圳股票交易代码大全
上证股票交易代码大全

查看源代码:

1
2
3
4
5
<li><a href="/gupiao/600151/">航天机电(600151)</a></li>
<li><a href="/gupiao/600156/">华升股份(600156)</a></li>
<li><a href="/gupiao/600160/">巨化股份(600160)</a></li>
<li><a href="/gupiao/600161/">天坛生物(600161)</a></li>
<li><a href="/gupiao/600162/">香江控股(600162)</a></li>

东方财富网每日股票交易信息单只股票:

quote.eastmoney.com/sh600000.html 上证
quote.eastmoney.com/sz000017.html 深圳或创业板

该页面查看源代码,看不到 12.17, 12.51等交易数据。说明源代码里面包含javascript程序,
浏览器执行javascript程序以后,才能得到显示的页面。

因此python程序需要在取到网页后,还要执行里面的javascript程序,才能得到股票数据

用requests.get无法得到显示的网页。必须用selenium或者pyppeteer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import re
import asyncio # Python 3.6之后自带的协程库
import pyppeteer as pyp
import bs4

async def antiAntiCrawler(page): # 为page添加反反爬虫手段
await page.setUserAgent('Mozilla/5.0 (Windows NT 6.1; \
Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) \
Chrome/78.0.3904.70 Safari/537.36')
await page.evaluateOnNewDocument(
'() =>{ Object.defineProperties(navigator, \
{ webdriver:{ get: () => false } }) }')


# 用正则表达式获取股票名称和代码
async def getStockCodes(page):
#从"https://www.banban.cn/gupiao/list_sh.html"对应的page获取所有股票名称和代码
codes = []
# 最终内容: ["四川路桥(600039)","包钢股份(600010)"......]
html = await page.content()
pt = '<a href="/gupiao/[^"]*">([^<]*\(\d+\))</a>'
# 对应 <li><a href="/gupiao/600151/">航天机电(600151)</a></li>
for x in re.findall(pt, html):
codes.append(x)
return codes
# 耗时: 0: 00:00.033943

async def getStockInfo(url):
browser = await pyp.launch(headless=False)
# 启动Chromium,browser即为Chromium浏览器,非隐藏启动
page = await browser.newPage() #在浏览器中打开一个新页面(标签)
await antiAntiCrawler(page) #新页面生成后一律调用此来反反爬
await page.goto(url) #装入url对应的网页
codes = await getStockCodes(page)
for x in codes[:3]: #只取前三个股票信息
print("-----", x) #x形如"四川路桥(600039)"
pos1, pos2 = x.index("("), x.index(")")
code = x[pos1 + 1:pos2] # 取股票代码,如600039
url = "https://quote.eastmoney.com/sh" + code + ".html"
await page.goto(url)
html = await page.content() # 往下编程前可以先print(html)看一看
pt = '<td>([^<]*)</td>.*?<td[^>]*id="gt\d*?"[^>]*>([^<]*)</td>'
for x in re.findall(pt, html, re.DOTALL):
print(x[0], x[1])
await browser.close() # 关闭浏览器


url = "https://www.banban.cn/gupiao/list_sh.html"
loop = asyncio.get_event_loop()
loop.run_until_complete(getStockInfo(url))
1
2
html = await page.content()
print(html) #打出的内容拷贝到记事本查看,找到以下内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<tr>
<td>今开: </td>
<td id="gt1" class="txtl" data-bind="46">1.22</td>
<td>最高: </td>
<td id="gt2" class="txtl" data-bind="44">1.22</td>
<td>涨停: </td>
<td id="gt3" class="txtl red" data-bind="51">1.34</td>
<td>换手: </td>
<td id="gt4" class="txtl" data-bind="tr">1%</td>
<td>成交量: </td>
<td id="gt5" class="txtl" data-bind="47">316.4万手</td>
.....................
<td id="gt6" class="txtl" data-bind="pe">245.41</td>
<td>总市值: </td>
<td id="gt7" class="txtl" data-bind="tmv">547.0亿</td>
</tr>

正则表达式pt对应:

1
2
<td>今开: </td>
<td id="gt1" class="txtl" data-bind="46">1.22</td>

输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
----- 包钢股份(600010)
今开: 1.22
最高: 1.22
涨停: 1.34
换手: 1%
成交量: 316.4万手
总市值: 547.0亿
昨收: 1.22
最低: 1.19
跌停: 1.1
量比: 1.04
成交额: 3.81亿
市净: 1.03
流通市值: 380.1亿
----- 四川路桥(600039)
今开: 4.94
最高: 4.95
涨停: 5.43
换手: 0.49%
成交量: 17.64万手
总市值: 232.8亿
昨收: 4.94
最低: 4.86
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#用BeautifulSoup获取股票名称和代码
async def getStockCodes(page):
codes = []
html = await page.content()
soup = bs4.BeautifulSoup(html, "html.parser")
for x in soup.find_all("li"):
#对应 <li><a href="/gupiao/600151/">航天机电(600151)</a></li>
a = x.find("a")
if( "(" in a.text and ")" in a.text):
codes.append(a.text)
return codes
#耗时: 0: 00:00.193480

#用浏览器自身的查找元素功能获取股票名称和代码
async def getStockCodes(page):
codes = []
elements = await page.querySelectorAll("li") #根据tag name找元素
#对应 <li><a href="/gupiao/600151/">航天机电(600151)</a></li>
for e in elements:
a = await e.querySelector("a") #根据tag name找元素
obj = await a.getProperty("text") #还可以 a.getProperty("href")
#上面这句不行就改成: obj = await a.getProperty("innerText")
text = await obj.jsonValue() #固定写法
if( "(" in text and ")" in text):
codes.append(text)
return codes
#耗时: 0:00:04.421178

弹出菜单点“检查”,可以看到附近元素对应的源代码(查看源代码看不到)

需要登录的爬虫

  • 许多网站需要登录后才能访问其内容

    京东、淘宝需要登录才能访问交易记录
    openjudge.cn 需要登录才能看提交过的源代码

  • 登录操作,无法用一个url表示出来

  • 解决办法之一:用浏览器模拟登录过程,输入用户名密码、点登录按钮。或者程序启动浏览器,等待手工登录后,程序再继续爬虫操作(对有验证码的情况)

爬取Openjudge自己提交通过的所有程序源码

  • 程序命令浏览器模拟登录过程,即输入用户名密码、点登录按钮
  • 或:程序启动浏览器,等待手工登录后,程序再继续爬虫操作(对有验证码的情况,或者懒得写代码的情况)
  • 更高级做法:不用浏览器,经数据包分析后,用requests库进行数据传输进行登录

鼠标右键点击右上角的“个人首页”,在弹出的菜单上选“检查” :

点击 “个人首页”, 进入:

1
<a href="http://cxsjsxmooc.openjudge.cn/2020t1fallall2/solution/25212869/" class="result-right">Accepted</a>

点击某个题的“Accepted”链接,进入:

1
2
3
4
5
6
7
8
9
<pre class="sh_python">
n = int(input())
lst = []
for i in range(n):
s = input().split()
lst.append((s[0], int(s[1])))
lst.sort(key= lambda x : (-x[1], x[0]))
for x in lst:
print(x[0], x[1])</pre>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import asyncio
import pyppeteer as pyp

async def antiAntiCrawler(page):
# 为page添加反反爬虫手段
await page.setUserAgent('Mozilla/5.0 (Windows NT 6.1; Win64; x64) \ ' 'AppleWebKit/537.36 (KHTML, like Gecko) ' 'Chrome/78.0.3904.70 Safari/537.36')
await page.evaluateOnNewDocument(
'() =>{ Object.defineProperties(navigator,'
'{ webdriver:{ get: () => false } }) }')

async def getOjSourceCode(loginUrl):
width, height = 1400, 800 #网页宽高
browser = await pyp.launch(headless=False,
userdataDir="c:/tmp",
args=[f'--window-size={width},{height}'])
page = await browser.newPage()
await antiAntiCrawler(page)
await page.setViewport({'width': width, 'height': height})
await page.goto(loginUrl)

# 若手动登录,则以下若干行可以去掉
element = await page.querySelector("#email") # 找到账户输入框
await element.type("XXXXX@qq.com") # 输入邮箱
element = await page.querySelector("#password") # 找到密码输入框
await element.type("XXXXXXXXX") # 输入密码
element = await page.querySelector("#main > form > div.user-login > p:nth-child(2) > button") #找到登录按钮
await element.click() # 点击登录按钮
# 若手动登录,则以上若干行可以去掉。time.sleep(10)或等待某个元素出现

await page.waitForSelector("#main>h2", timeout=30000) # 等待“正在进行的比赛...."标题出现

element = await page.querySelector("#userMenu>li:nth-child(2)>a")
# 找"个人首页”链接
await element.click() # 点击个人首页链接
await page.waitForNavigation() # 等新网页装入完毕

elements = await page.querySelectorAll(".result-right")
# 找所有"Accepted"链接, 其有属性 class="result-right"
page2 = await browser.newPage() # 新开一个页面 (标签)
await antiAntiCrawler(page2) #添加反反爬
for element in elements[:2]: # 只打印前两个程序
obj = await element.getProperty("href") # 获取href属性
url = await obj.jsonValue()
await page2.goto(url) # 在新页面(标签)中装入新网页
element = await page2.querySelector("pre") # 查找pre tag
obj = await element.getProperty("innerText") # 取源代码
text = await obj.jsonValue()
print(text)
print("-------------------------")
await browser.close()

def main():
url = "http://openjudge.cn/auth/login/"
syncio.get_event_loop().run_until_complete(getOjSourceCode(url))

main()

pyppeteer+requests编写快速爬虫

  • requests要对付登录比较麻烦(要用到抓包等技巧)
  • pyppeteer没有requests快(因为要浏览器渲染网页)
  • 对于需要登录,且登录后的网页都不是javascript生成的动态网页的情况,可以使用pyppeteer登录后,再用requests做剩下的事情。

网址: http://openjudge.cn/user/2312/

不登录,访问同样网址,提示没有登录
同样的访问请求,服务器怎么知道浏览器是否登录过?

预备知识: cookie和session

  • 登录成功后,服务器向浏览器发送一些身份标识数据,称为cookie,浏览器以后每次向服务器发送请求,都带上cookie,服务器就能知道请求来自前面那个登录的浏览器了。

  • 服务器在内存为浏览器维护一个session,每个浏览器对应不同的session,里面存放着该浏览器的状态(比如一系列的填表等步骤已经进行到什么程度),不同的session有不同的session id,浏览器发送请求的时候,如果带上session id,服务器也能知道是哪个浏览器在请求。

  • 在客户计算机上由cookie可以生成标识同一个浏览器的session。

工作原理

  • pyppeteer的浏览器的页面有cookies()函数可以获得cookie
  • requests.Session()可以生成一个空session
  • session的cookies.update(cookies)函数可以根据cookies生成相应session
  • session的get(url)函数,可以向服务器发送带session的请求
  • 获得cookie,生成相应session以后,爬取网页都用session的get函数进行(前提:网页不是javascript生成的。如果是,依然用pyppeteer的浏览器爬取)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
import asyncio
import pyppeteer as pyp
import bs4
import requests

def sessionGetHtml(session, url): # 发送带session的网页请求
fakeHeaders = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) \
AppleWebKit/537.36 (KHTML, like Gecko) \
Chrome/81.0.4044.138 Safari/537.36 Edg/81.0.416.77'
} # 伪装浏览器用的请求头
try:
result = session.get(url, headers=fakeHeaders)
result.encoding = result.apparent_encoding
return result.text
except Exception as e:
print(e)
return ""

async def makeSession(page):
# 返回一个session,将其内部cookies修改成pypeteer浏览器页面对象中的cookies
cookies = await page.cookies() #cookies是一个列表,每个元素都是一个字典
cookies1 = {}
for cookie in cookies: # requests中的cookies只要"name"属性
cookies1[cookie['name']] = cookie['value']
session = requests.Session()
session.cookies.update(cookies1)
return session

async def antiAntiCrawler(page):
#为page添加反反爬虫手段
await page.setUserAgent('Mozilla/5.0 (Windows NT 6.1; Win64; x64) \ '
'AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/78.0.3904.70 Safari/537.36')
await page.evaluateOnNewDocument(
'() =>{ Object.defineProperties(navigator,'
'{ webdriver:{ get: () => false } }) }')

async def getOjSourceCode(loginUrl):
width, height = 1400, 800 #网页宽高
browser = await pyp.launch(headless=False,
userdataDir = "c:/tmp",
args=[f'--window-size={width},{height}'])
page = await browser.newPage()
await antiAntiCrawler(page)
await page.setViewport({'width': width, 'height': height})
await page.goto(loginUrl)
await page.waitForSelector("#main>h2",
timeout=30000) #等待手动登录后,“正在进行的比赛...."标题出现
element = await page.querySelector("#userMenu>li:nth-child(2)>a")
#找"个人首页”链接
await element.click() #点击个人首页链接
await page.waitForNavigation() #等新网页装入完毕

elements = await page.querySelectorAll(".result-right")
#找所有"Accepted"链接, 其有属性 class="result-right"
session = await makeSession(page)
for element in elements[:2]:
obj = await element.getProperty("href")
url = await obj.jsonValue()
html = sessionGetHtml(session, url)
soup = bs4.BeautifulSoup(html, "html.parser")
element = soup.find("pre")
print(element.text)
print("-------------------------")
await browser.close()

def main():
url = "http://openjudge.cn/auth/login/"
asyncio.get_event_loop().run_until_complete(getOjSourceCode(url))
main()

补充tips

绝对网址和相对网址

  • 绝对网址以http:// 或 https:// 开头 ,相对网址无这两种开头
    如果当前网页网址是:
    http://www.pku.edu.cn/education/index.htm
    而该网页中有一个链接,其中网址是相对的,形如:
    <a href="dict/word.htm">词典单词</a>
    则该链接的真实网址(绝对网址)是:
    http://www.pku.edu.cn/education/dict/word.htm

  • 使用requests库时,获得当前网页网址:

1
2
3
4
5
6
r = requests.get("http://openjudge.cn")
print(r.url) #>>http://openjudge.cn
#或:
session = requests.session()
r = session.get("http://openjudge.cn")
print(r.url)
  • 使用pyppeteer库时,获得当前网页网址:
1
2
3
4
browser = await pyp.launch(headless=False)
page = await browser.newPage()
await page.goto("http://openjudge.cn")
print(page.url) #>>http://openjudge.cn

反反爬

  • 连续的两个操作之间,加入适当延时,模拟人的动作,以免因动作太快被识破
1
2
import time
time.sleep(2) #暂停2秒,啥也不做

也可以用time.sleep(...)来等待一段时间,确保网页加载完成

文章作者: HibisciDai
文章链接: http://hibiscidai.com/2023/02/02/实用Python程序设计MOOC-第十二章网络爬虫设计/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 HibisciDai
好用、实惠、稳定的梯子,点击这里