I’m Feeling Lucky

从 Google 上搜索一个关键词时,你会查看多个搜索结果,然后不断的点击,这很乏味。如果可以在命令行中输入查找关键词,让计算机自动打开浏览器,并在新的选项卡中显示前面几项查询结果,那就太方便了了。

首先,我们知道Google的搜索pattern是: https://www.google.com/search?q=Keywords

1
2
3
4
5
6
7
8
9
#! python3 
## lucky.py - Opens several Google search results.

import requests, sys, webbrowser
from bs4 import BeautifulSoup

print('Googling...') ## display text while downloading the Google page
res = requests.get('http://google.com/search?q=' + ' '.join(sys.argv[1:]))
res.raise_for_status()

然后我们会得到一个Google的搜索页面,接下来我们将使用BeautifulSoup模块来解析它,使用开发者工具找到Google搜索界面的结果链接规律,可以得到:

1
2
3
4
5
# Retrieve top search result links. 
soup = BeautifulSoup(res.text, 'lxml')

# Open a browser tab for each result.
linkElems = soup.select('div.rc>.r>a')

然后针对每个结果打开 Web 浏览器即可,

1
2
3
4
numOpen = min(5, len(linkElems)) 
## 默认情况下,在新的选项卡中会打开前5个查询结果,但如果你搜的乱七八糟,结果可能会小于5个,所以我们取最小的。
for i in range(numOpen):
webbrowser.open(linkElems[i].get('href'))

但Google还是不那么容易爬取的,我们需要增加一些东西,比如请求头headers,否则会出现:

1
UnicodeDecodeError: 'gbk' codec can't decode byte...

脚本如下:

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
import requests, sys, webbrowser
from bs4 import BeautifulSoup

keywords = ' '.join(sys.argv[1:])
# for better search results
query = keywords.replace(' ', '+')
# display text while downloading the Google page
print('Googling...' + query)

headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36',
}

res = requests.get('https://google.com/search?q=' + query, headers = headers)
try:
res.raise_for_status() ## 如果出错,将抛出异常
except Exception as exc:
print('There was a problem: %s' % (exc))
# Retrieve top search result links.
soup = BeautifulSoup(res.text,'lxml')

# Open a browser tab for each result.
linkElems = soup.select('div.rc>.r>a')
numOpen = min(10, len(linkElems))
for i in range(numOpen):
link = linkElems[i].get('href')
webbrowser.open(link)

接下去可能还需要selenium来爬取完整的搜索结果。

XKCD

xkcd是兰道尔·门罗(Randall Munroe)的网名,又是他所创作的漫画的名称。作者兰道尔·门罗(Randall Munroe)给作品的定义是一部“关于浪漫、讽刺、数学和语言的网络漫画”(A webcomic of romance,sarcasm, math, and language),被网友誉为深度宅向网络漫画。

英文站:https://xkcd.com/

中文站:https://xkcd.in/

ssa.jpg

接下来,我们需要下载所有 XKCD 漫画 ,首先看一下并不复杂的html代码。

fszf.jpg

我们使用os.makedirs创建一个目录(已经存在则不用创建)来存储下载好的XKCD漫画,

1
2
3
4
5
6
7
#! python3
# downloadXkcd.py - Downloads every single XKCD comic.

import requests, os, bs4

url = 'https://xkcd.com' # starting url
os.makedirs('xkcd', exist_ok=True) # store comics in ./xkcd

然后对网页进行解析,:

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
## https://xkcd.com/# 表示没有漫画了哦,循环结束
while not url.endswith('#'):
## 要下载的漫画的网址
print('Downloading page %s...' % url)
res = requests.get(url)
res.raise_for_status()

soup = bs4.BeautifulSoup(res.text,'lxml')

# Find the URL of the comic image.
comicElem = soup.select('#comic img')
## 有一些 XKCD 页面有特殊的内容,不是一个简单的图像文件,可以跳过
if comicElem == []:
print(' Could not find comic image from %s.'%(url))
else:
comicUrl = comicElem[0].get('src')
# Download the image.
print('Downloading image %s...' % (schema + comicUrl))
res = requests.get(schema + comicUrl)
res.raise_for_status()
## 保存图片到 ./xkcd
## 比如//imgs.xkcd.com/comics/dynamic_entropy.png
## 保存为网址ID + dynamic_entropy.png
imageFile = open(os.path.join('xkcd', re.sub("\D", "", url) + '_' + os.path.basename(comicUrl)), 'wb')
for chunk in res.iter_content(100000):
imageFile.write(chunk)
imageFile.close()

# Get the Prev button's url.
prevLink = soup.select('a[rel="prev"]')[0]
url = 'https://xkcd.com' + prevLink.get('href')

print('Done.')

我们还可以使用多线程来加快程序的下载速度,我们可以向目标函数传入参数,比如:

1
2
3
4
import threading 
threadObj = threading.Thread(target=print, args=['Cats', 'Dogs', 'Frogs'],
kwargs={'sep': ' & '})
threadObj.start()

现在我们的下载漫画程序是这样的,。程序运行的大部分时间,都用于建立网络连接来开始下载,以及将下载的图像写入硬盘每一次,它只能下载一幅漫画,浪费了大部分可用的带宽,而在多线程程序中有一些线程在下载漫画,同时另一些线程在建立连接,或将漫画图像文件写入硬盘,这将大大改善下载速度。

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
import requests, os, bs4, threading
os.makedirs('xkcd', exist_ok=True) # store comics in ./xkcd

def downloadXkcd(startComic, endComic):
## 循环遍历指定范围中的所有编号漫画
for urlNumber in range(startComic, endComic):
# Download the page.
print('Downloading page http://xkcd.com/%s...' % (urlNumber))
res = requests.get('https://xkcd.com/%s' % (urlNumber))
res.raise_for_status()

soup = bs4.BeautifulSoup(res.text)
comicElem = soup.select('#comic img')
if comicElem == []:
print('Could not find comic image.')
else:
comicUrl = comicElem[0].get('src')
print('Downloading image %s...' % (comicUrl))
res = requests.get(comicUrl)
res.raise_for_status()

imageFile = open(os.path.join('xkcd', os.path.basename(comicUrl)), 'wb')
for chunk in res.iter_content(100000):
imageFile.write(chunk)
imageFile.close()

## 创建一个空列表 downloadThreads,用于追踪创建的多个Thread 对象
downloadThreads = [] ## 所有线程的列表

for i in range(0, 1400, 100): ## 循环14次,创建14个线程
downloadThread = threading.Thread(target=downloadXkcd, args=(i, i + 99))
downloadThreads.append(downloadThread)
downloadThread.start()

## 阻塞, 等待所有线程结束
for downloadThread in downloadThreads:
downloadThread.join()
print('Done.')