使用 Python 爬取 motions 表情包

起因

前段时间在微信群中看到群友发这种表情包:

出于感兴趣就问这个群友还有没有更多这种表情(盗图王)
然后群友告诉我了这个表情包的官网地址,看着满屏的沙雕表情包就想爬取下来使用

编写爬虫

点击官网的 YES!!!!! 之后就可以看到全部表情包,随便右击一张图片一张图片复制URL后可以看到图片连接:
http://motions.cat/gif/nhn/0106.gif
然后再右击它相邻的一张复制出链接:
http://motions.cat/gif/nhn/0105.gif
通过上面两个链接我们可以分析出图片路由是按照递增的规则进行存储 GIF 图片的,并且是四位数前置补0

所以我们可以通过自增一个数字然后使用格式化输出匹配出路由,使用python3测试一下:

script
1
2
3
4
$ python3
>>> number = 1
>>> '%04d' % number # %04d: 输出一个整数的时候 按照4位数对其 多余的使用 0 补齐
'0001'

开始编写 Python 代码:
首先定义前置 URL 这一块是不会变的,然后定义启始页

1
2
3
url = 'http://motions.cat/gif/nhn/'
start_number = 1
max_number = 139

随后我们写一个死循环,每循环一次就对 start_number 进行自增,当 start_number 自增到大于 max_number 的时候就退出循环,然后在循环中编写爬虫

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
while True:
if (start_number > max_number):
break

try:
filename = ('%04d' % start_number) + '.gif'
requestURL = url + filename
print('开始请求:', requestURL)
r = requests.get(requestURL)
if (r.status_code != 200):
print(requestURL + ', Request fail, status code: ', r.status_code)
continue
# 存储目录,目录不存在就创建
path = './motions/'
if not os.path.exists(path):
os.mkdir(path)

# 将请求到的二进制数据写入到 GIF 文件中
with open(path + filename, 'wb') as f:
f.write(r.content)
print('保存成功:', filename)

start_number += 1
except Exception as e:
print('Error: ', e)

完整代码:

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
import requests
import os

def main():
url = 'http://motions.cat/gif/nhn/'
start_number = 1
max_number = 139

while True:
if (start_number > max_number):
break

try:
filename = ('%04d' % start_number) + '.gif'
requestURL = url + filename
print('开始请求:', requestURL)

r = requests.get(requestURL)
if (r.status_code != 200):
print(requestURL + ', Request fail, status code: ', r.status_code)
continue

path = './motions/'
if not os.path.exists(path):
os.mkdir(path)

with open(path + filename, 'wb') as f:
f.write(r.content)
print('保存成功:', filename)

start_number += 1
except Exception as e:
print('Error: ', e)

if ('__main__' == __name__):
main()

运行效果:

由于该网站运行在国外,所以爬的很慢所以我打算编写多线程代码交替执行爬虫来提高爬虫效率。

编写多线程爬虫

Python 的标准库提供了 threading 模块用于编写多线程,开启线程使用 threading.Thread 类创建一个实例,然后通过调用 Thread.start() 方法执行线程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import threading

def methodName(methodArgs1, methodArgs2):
# 调用 threading.current_thread().name 可以看到当前线程的名称
print("currency thread name:", threading.current_thread().name)

# 创建一个 Thread 对象
# target 是需要执行的方法,name 给线程起一个名字,args 是方法的参数
thread = threading.Thread(
target=methodName,
name='线程名称'
args=(methodArgs1, methodArgs2)
)
thread.start() # 执行线程
thread.join() # 回到主线程中继续执行

多线程爬虫完整代码:

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
import requests
import queue
import threading
import os

def crawl_motions(taskQueue):
while not taskQueue.empty():
number = taskQueue.get()
print('正在下载第' + str(number) + '张表情包', threading.currentThread().name)

filename = ('%04d' % number) + '.gif'
url = 'http://motions.cat/gif/nhn/' + filename
try:
r = requests.get(url)

if r.status_code != 200:
print('请求第' + url + '失败', 'Thread name: ', threading.currentThread().name)
taskQueue.put(number)

store_path = './motions-test/'
if not os.path.exists(store_path):
os.mkdir(store_path)

with open(store_path + filename, 'wb') as f:
f.write(r.content)
print("保存成功" + filename, 'Thread name:', threading.currentThread().name)
except Exception as e:
taskQueue.put(number)
print('Error: ', e, 'Thread name: ', threading.currentThread().name)


# def store_data(dataQueue):



def main():
taskQueue = queue.Queue()
start_number = 1
max_number = 139

for i in range(start_number, max_number):
taskQueue.put(i) # 将需要爬取的 GIF 放到任务队列

crawl_thread = []
for i in range(1, 10):
# 创建线程
threading_crawl = threading.Thread(
target=crawl_motions,
name='crawl-' + str(i),
args=(taskQueue,)
)
crawl_thread.append(threading_crawl)
# 开启线程
threading_crawl.start()

for thread in crawl_thread:
thread.join()


if '__main__' == __name__:
main()
print("OK!")

执行效果:

使用 Charles 对电脑和手机进行抓包

Charles 简介

Charles 是一款 HTTP 代理/ HTTP 监听/ 方向代理服务器软件。开发人员可以使用 Charles 看到计算机和互联网之间的 HTTP 和 HTTPS 信息。包括请求,响应和 HTTP 请求头(Cookie和缓存信息).

安装和使用 Charles

点击链接 https://www.charlesproxy.com/download/ 下载对应的版本进行安装

macOS 版本为例:

  • 下载 dmg 文件 点击此处可下载4.5.6版本
  • 双击打开 dmg 文件,点击 accept 然后把应用文件拖动到 Applications 目录。
  • 随后就可以在启动台打开 Charles。
  • 启动 Charles 后会出现如下弹窗索取权限,点击Grant Privileges 同意后输入密码同意授权。
    automagic
  • 随后勾选 Proxy - macOS Proxy 启动系统代理。然后使用浏览器访问百度就可以在左侧看到我们刚才请求百度的 HTTP 请求信息。
    macOS Proxy
    Charles

安装 SSL 证书

进过以上步骤安装后可以看到百度的请求下面出现 unknown,原因是百度使用的是 HTTPS 使用 SSL 经过加密传输,我们只需安装上 Charles 的证书即可获取到请求信息。

选择 Help - SSL Proxying - Install Charles Root Certificate
Install certificate
点击之后会弹出一个添加证书的弹窗选择添加即可
Add certificate
添加之后我们打开钥匙串访问这个系统软件,在右上方搜索 Charles 就可以找到刚才安装的证书,安装之后是不信任的证书。
钥匙串访问
双击安装的证书,点开信任一栏,选择始终信任,然后关闭窗口输入密码。如图所示:
信任证书

配置 SSL 代理

进入 Proxy - SSL Proxying Settings 点击 Add 添加需要监听的域名和端口,支持 * 通配符 SSL 通常是 443 端口。
添加 SSL 代理

这里我们添加两个配置, 即可应用绝大多数 HTTPS 请求,添加完成之后就可以愉快的抓取 HTTPS 请求了。

  • *:443
  • *:*
    SSL Proxying Settings

再次访问百度点开请求路径选择 Contents 一栏就可以请求和响应信息。
Charles

抓包IOS手机

配置代理

首先确保手机和电脑出于同一网络内,点击 Help - Local IP Address 查看电脑IP,点击 Proxy - Proxy Settings 查看代理端口默认是 8888。

打开手机进入 设置 - 无线局域网 - 点击已连接上WI-FI右侧的感叹号。(确保电脑和手机处于局域网内!)
滑倒最下面点击 配置代理 选择手动,填入 IP 和 端口信息。如下图所示:
IOS配置代理

随后 Charles 会弹出一个连接请求点击 Allow (如果点击到了 Deny 请重启 Charles)

打开手机浏览器访问百度之后会提示 此连接非私人连接 是因为我们手机还未安装 Charles 的证书。

IOS 安装 Charles 证书

打开 Charles 选择 Help - SSL - Install Charles Root Certificate on a Mobile Device or Remote Browser

然后手机使用 Safari 浏览器打开 chls.pro/ssl 点击允许安装描述文件。确保设备处于局域网下并配置代理正确。

随后手机进入 设置 - 通用 - 描述文件 可以看到多了一个 Charles Proxy CA 的描述文件,点击该描述文件,点击右上角的安装。

安装完成后进入 设置 - 通用 - 关于本机 - 证书信任设置(最下面) 点击信任 Charles Proxy CA 的证书。
至此手机的证书设置就完成了。

抓包微信小程序

如果你只需要抓取手机上的请求可以暂时关闭 Proxy - macOS Proxy(快捷键:Shift + command + P) 减少电脑请求对手机抓包的干扰。

打开一个小程序(本文使用微信指数小程序)
操作小程序发送一个请求
微信指数
打开 Charles 查看请求信息即可看到响应的 json 信息

PHP数组访问

ArrayAccess 接口

平常使用的 Laravel 框架的模型对象既可以使用箭头来进行赋值, 又可以使用数组方式进行赋值.

1
2
3
4
5
6
<?php

$user = new Users();

$user->name = "RandyChan";
$user['email'] = "M17607475471@163.com";

通过以上两种方式都能给对象赋值, 这个功能主要是实现了 ArrayAccess 接口和 __get__set 这两个魔术方法.

ArrayAccess

实现 ArrayAccess 接口需要实现四个方法.

  • offsetExists(mixed $offset): boolean : 检查一个偏移位置是否存在
  • offsetGet(mixed $offset): mixed: 获取一个偏移位置的值
  • offsetSet(mixed $offset, mixed $value): void: 设置一个偏移位置的值
  • offsetUnset(mixed $offset): 复位一个偏移位置的值

其中 $offset 参数代表数组的键, $value 参数代表数组的值

实现 ArrayAccess 接口

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
72
73
74
75
76
77
<?php

class User implements \ArrayAccess
{
/**
* 声明一个数组存储数据
* @var array
*/
private $data = [];

/**
* 检查一个偏移位置是否存在, 只需判断这个键是否存在定义好的data变量里面
* @param mixed $offset
* @return bool|mixed
*/
public function offsetExists($offset)
{
return isset($this->data[$offset]);
}

/**
* 获取一个偏移位置的值, 实现该方法只需要返回 data 数组对应的值就行
* @param mixed $offset
* @return mixed
*/
public function offsetGet($offset)
{
return $this->data[$offset];
}

/**
* 设置一个偏移位置的值, 往 data 里面设置就行
* @param mixed $offset
* @param mixed $value
*/
public function offsetSet($offset, $value)
{
$this->data[$offset] = $value;
}

/**
* 复位一个偏移位置的值, 删除数组里面的值, 这里先判断一下值是否存在
* @param mixed $offset
*/
public function offsetUnset($offset)
{
if ($this->offsetExists($offset)) {
unset($this->data[$offset]);
}
}

/**
* 当设置一个不存在的属性时触发
* @param mixed $offset
* @param mixed $value
*/
public function __set($offset, $value)
{
$this->data[$offset] = $value;
}

/**
* 当获取一个不存在的属性是触发
* @param mixed $offset
* @return mixed
*/
public function __get($offset)
{
return $this->data[$offset];
}
}

$user = new User();
$user['name'] = "RandyChan";
$user->email = "M17607475471@163.com";

echo $user->name . "\n" . $user['email'];

执行结果

1
2
RandyChan
M17607475471@163.com

PHP实现冒泡排序

冒泡排序 (Bubble Sort)

冒泡排序(英语:Bubble Sort)是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序(如从大到小、首字母从A到Z)错误就把他们交换过来。

演示动画:
冒泡排序

PHP代码实现:

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
<?php

/**
* main
* @return void
*/
function main()
{
$array = [3, 1, 4, 5, 8, -1, 7, 6, 4, 2, 3];
bubbleSort($array, count($array) - 1);
fwrite(STDOUT, "排序结果: " . PHP_EOL);
foreach ($array as $value) {
fwrite(STDOUT, $value . PHP_EOL);
}
}

/**
* 冒泡排序
* @param $array
* @param $length
* @return void
*/
function bubbleSort(array &$array, int $length)
{
for ($i = 0; $i < $length; ++$i) {
for ($j = 0; $j < $length - $i; ++$j) {
if ($array[$j] > $array[$j + 1]) {
swap($array[$j], $array[$j + 1]);
}
}
}
}

/**
* 交换两个值
* @param mixed $firstVariable
* @param mixed $lastVariable
* @return void
*/
function swap(&$firstVariable, &$lastVariable)
{
$temp = $firstVariable;
$firstVariable = $lastVariable;
$lastVariable = $temp;
}

main();

PHP实现选择排序

排序算法(Selection Sort)

选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到全部待排序的数据元素排完。 选择排序是不稳定的排序方法。

动画演示:

选择排序

PHP代码实现:

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
<?php

/**
* main
* @return void
*/
function main()
{
$array = [3, -1, 4, 5, 8, 7, 9, 6, 4, 2, 3];
selectionSort($array, count($array));

fwrite(STDOUT, "排序结果: \n");
foreach ($array as $value) {
fwrite(STDOUT, $value . "\n");
}
}

/**
* 选择排序
* @param array $array
* @param int $length
* @return void
*/
function selectionSort(array &$array, int $length)
{
for ($i = 0; $i < $length - 1; $i ++) { // 由于每次都是和最后面的数字进行比较,所以最后一位不需要循环
$min = $i;
for ($j = $i + 1; $j < $length; $j ++) { // 循环未排序号的数字
if ($array[$j] < $array[$min]) {
$min = $j;
}
}
swap($array[$i], $array[$min]);
}
}

/**
*
* @param mixed $firstVariable
* @param mixed $lastVariable
*/
function swap(&$firstVariable, &$lastVariable)
{
$temp = $firstVariable;
$firstVariable = $lastVariable;
$lastVariable = $temp;
}

main();

Hello World

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

十个推荐使用的Laravel函数

Laravel中包含各种全局辅助函数。可以使用它们来简化开发流程。在这里,我将编写10个最好用的laravel帮助函,数用于使我的开发更容易。 您必须考虑在必要时使用它们。

您还可以查看中文文档发现更多的辅助函数Laravel help functions

array_dot()


array_dot() 辅助函数允许你将多维数组转换为使用点符号的一维数组。

1
2
3
4
5
6
7
8
$array = [
'user' => ['username' => 'something'],
'app' => ['creator' => ['name' => 'someone'], 'created' => 'today']
];

$dot_array = array_dot($array);

// [user.username] => something, [app.creator.name] => someone, [app.created] => today

array_get()


array_get() 函数使用点符号从多维数组中检索值。

1
2
3
4
5
6
7
8
$array = [
'user' => ['username' => 'something'],
'app' => ['creator' => ['name' => 'someone'], 'created' => 'today']
];

$name = array_get($array, 'app.creator.name');

// someone

如果key不存在, array_get() 函数还接受可选的第三个参数作为默认值。

1
2
3
$name = array_get($array, 'app.creator.name', 'anonymous');

// anonymous

public_path()


public_path() 返回 Laravel 应用程序中公共目录的完全限定的绝对路径。 你还可以将路径传递到公共目录中的文件或目录以获取该资源的绝对路径。 它将简单地将 public_path() 添加到你的参数中。

1
2
3
4

$public_path = public_puth();

$path = public_path('js/app.js');

Str::orderedUuid()


Str::orderedUuid()函数首先生成一个时间戳 uuid。 这个 uuid 可以存储在索引数据库列中。 这些 uuid 是基于时间戳创建的,因此它们会保留你的内容索引。 在 Laravel 5.6 中使用它时,会引发 Ramsey\Uuid\Exception\UnsatisfiedDependencyException。 要解决此问题,只需运行以下命令即可使用 moontoast/math 包:

1
composer require "moontoast/math"
1
2
3
4
5
use Illuminate\Support\Str;

return (string) Str::orderByUuid()

// A timestamp first uuid

str_plural()


str_plural() 函数将字符串转换为复数形式。该功能只支持英文。

1
2
3
4
5
6
7
echo str_plural('bank');

// banks

echo str_plural('developer');

// developers

route()


route() 函数为指定的路由生成路由 URL。

1
$url = route('login');

如果路由接受参数,你可以简单地将它们作为第二个参数传递给一个数组。

1
$url = route('products', ['id' => 1]);

如果你想产生一个相对的 URL 而不是一个绝对的 URL,你可以传递 false 作为第三个参数。

1
$url = route('products', ['id' => 1], false);

tap()


tap() 函数接受两个参数:一个值和一个闭包。该值将被传递给闭包,然后该值将被返回。闭包返回值无关紧要。

1
2
3
4
5
6
7
$user = App\User::find(1);

return tap($user, function($user) {
$user->update([
'name' => 'Random'
]);
});

它不会返回布尔值,而是返回 User Model

如果你没有传递闭包,你也可以使用 User Model 的任何方法。 无论实际返回的方法如何,返回值都将始终为值。 在下面的例子中,它将返回 User Model 而不是布尔值。 update 方法返回布尔值,但由于用了 tap ,所以它将返回 User Model

1
2
3
4
5
$user = App\User::find(1);

return tap($user)->update([
'name' => 'SomeName'
]);

dump()


dump() 函数会 dump 给定的变量,同时也支持同时传入多个变量。这对调试非常有用。

1
2
dump($var1);
dump($var1, $var2, $var3);

str_slug()


str_slug() 函数将给定的字符串生成一个 URL 友好的 slug。 你可以使用此功能为帖子或产品标题创建一个 slug。

1
2
3
$slug = str_slug('Helpers in Laravel', '-');

// helpers-in-laravel

optional()


optional() 函数接受一个参数,你可以调用参数的方法或访问属性。 如果传递的对象为 null,则方法和属性将返回 null,而不是导致错误或抛出异常。

1
2
3
$user = User::find(1);

return optional($user)->name;

原文地址: https://tutsforweb.com/10-best-laravel-helpers/

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×