豪翔天下

Change My World by Program

0%

即使我写了这样的文章,我依然坚信设计模式只是某些垃圾语言用来规避其语言本身弊端的规范而已,不然,就只能写出糟糕的代码了。

创建型

Factory Method(工厂方法模式/工厂模式)

工厂模式是一种类,提供了创建对象的某些方法,可以直接使用工厂类创建对象,而不是直接使用new。

优点:如果要改变所创建对象的类型,只需要修改该工厂即可。比如有个类需要读取用户数据来创建,原本是读取的数据库,现在要从文本读取,就得把那个类及其所有依赖都更改一遍。

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
<?php
interface IPost{
function getName();
}

class Post implements IPost{
public function __construct($id){}

public static function Load($id){
return new Post($id);
}

public static function Create(){
return new Post(null);
}

public function getName(){
return "haofly";
}
}

$post = Post::Load(1);
echo $post->getName();
?>

AbstractFactory(抽象工厂模式)

抽象工厂模式提供了一种方式,可以将一组具有同一主题的单独的工厂封装起来。客户端程序不需要知道(或关心)它从这些内部的工厂方法中获得对象的具体类型,因为客户端程序仅使用这些对象的通用接口。抽象工厂模式将一组对象的实现细节与他们的一般使用分离开来。

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<?php
abstract class AbstractFactory {
abstract public function CreateButton();
abstract public function CreateBorder();
}

class MacFactory extends AbstractFactory{
public function CreateButton()
{

return new MacButton();
}
public function CreateBorder()
{
return new MacBorder();
}
}
class WinFactory extends AbstractFactory{
public function CreateButton()
{
return new WinButton();
}
public function CreateBorder()
{
return new WinBorder();
}
}
class Button{}
class Border{}

class MacButton extends Button{
function __construct()
{
echo 'MacButton is created' . "\n";
}
}
class MacBorder extends Border{
function __construct()
{
echo 'MacBorder is created' . "\n";
}
}


class WinButton extends Button{
function __construct()
{
echo 'WinButton is created' . "\n";
}
}
class WinBorder extends Border{
function __construct()
{
echo 'WinBorder is created' . "\n";
}
}
?>
# 客户端使用
<?
$type = 'Mac'; //value by user.
if(!in_array($type, array('Win','Mac')))
die('Type Error');
$factoryClass = $type.'Factory';
$factory=new $factoryClass;
$factory->CreateButton();
$factory->CreateBorder();
?>

Prototype(原型模式)

通过复制一个已经存在的实例来返回新的实例,而不是新建实例。

优点: 多用于创建复杂的或者耗时的实例,这种情况,复制一个已经存在的实例使程序运行更高效,活着创建值相等,只是命名不一样的同类数据。

Singleton(单例模式)

php示例,代码来自IBM

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
<?php
require_once("DB.php");

class DatabaseConnectiono{
private $_handle = null;

private function __construct(){
$dsn = 'mysql://root:password@localhost/photos';
$this->_handle = & DB::Connect($dsn, array());
}

public static function get(){
static $db = null;
if($db == null)
$db = new DatabaseConnectioni();
return $db;
}

public function handle(){
return $this->_handle;
}
}

print("Handle=".DatabaseConnection::get()->handle()."\n");
print("Handle=".DatabaseConnection::get()->handle()."\n");

结构型

Adapter Class/Object(适配器/转换器)

介绍: 把一个类的接口变换成客户端所期待的另一种接口,Adapter模式使原本因接口不匹配或不兼容而无法在一起工作的两个类能够在一起工作。外部请求方式一样,内部实现方式不一样。

应用场景:

  1. 想使用一个已经存在的类,但是它的接口并不完全符合需求
  2. 适用于第三方库的API会发生改变而选择不直接把第三方API给用户使用的情况下,在前面封装一层。

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
<?php  
/**
* 目标角色
*/
interface Target {
/**
* 源类的方法:这个方法将来有可能继续改进
*/
public function hello();

/**
* 目标点
*/
public function world();
}

/**
* 源角色:修改后的方法
*/
class Adapter {
/**
* 源类含有的方法
*/
public function world() {
echo ' world <br />';
}

/**
* 将hello方法改为了greet方法
*/
public function greet() {
echo ' Greet ';
}
}

/**
* 类适配器角色
*/
class Adapter implements Target {
private $_adaptee;

public function __construct(Adaptee $adaptee) {
$this->_adaptee = $adaptee;
}

/**
* 源类中没有hello方法,在此补充
*/
public function hello() {
$this->_adaptee->greet();
}
}
/**
* 客户端程序
*/
class Client {
public static function main() {
$adaptee = new Adaptee();
$adapter = new Adapter($adaptee);
$adapter->hello(); // 无论内部怎么变,外部都用hello来引用
$adapter->world();
}
}

Client::main();
?>

Bridge(桥接模式)

把事务对象和其具体行为、具体特征分离开来,使它们可以各自独立的变化。

python实例,代码来自维基百科

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
"""圆形、三角形归于抽象的形状,而画圆、画三角形归于抽象的画图"""

# Implementor
class DrawingAPI:
def drawCircle(x, y, radius):
pass

# ConcreteImplementor 1/2
class DrawingAPI1(DrawingAPI):
def drawCircle(self, x, y, radius):
print "API1.circle at %f:%f radius %f" % (x, y, radius)

# ConcreteImplementor 2/2
class DrawingAPI2(DrawingAPI):
def drawCircle(self, x, y, radius):
print "API2.circle at %f:%f radius %f" % (x, y, radius)

# Abstraction
class Shape:
# low-level
def draw(self):
pass

# high-level
def resizeByPercentage(self, pct):
pass

# Refined Abstraction
class CircleShape(Shape):
def __init__(self, x, y, radius, drawingAPI):
self.__x = x
self.__y = y
self.__radius = radius
self.__drawingAPI = drawingAPI

# low-level i.e. Implementation specific
def draw(self):
self.__drawingAPI.drawCircle(self.__x, self.__y, self.__radius)

# high-level i.e. Abstraction specific
def resizeByPercentage(self, pct):
self.__radius *= pct

def main():
shapes = [
CircleShape(1, 2, 3, DrawingAPI1()),
CircleShape(5, 7, 11, DrawingAPI2())
]

for shape in shapes:
shape.resizeByPercentage(2.5)
shape.draw()

if __name__ == "__main__":
main()

Composite(组合模式)

Decorator(装饰模式)

Facade(外观)

Flywight(享元)

Proxy(代理)

行为型

Interpreter(解释器)

Template Method(模板方法)

Observer(观察者模式)

优点: 被观察对象不用具体了解观察者具体实现,而是由观察者去实现。被观察者忽略了依赖它的对象,它要关注在事件发生时触发该事件并发送消息给观察者即可。这个和依赖注入有点相近的地方。

php示例,代码来自IBM

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
<?php
/**
* 测试代码创建 UserList,并将 UserListLogger 观察者添加到其中。然后添加一个消费者,并将这一更改通知 UserListLogger
*/

# 定义要通过怎样的方法才能成为观察者
interface IObserver{
function onChanged($sender, $args);
}
# 定义可以被观察的对象
interface IObservable{
function addObserver($observer);
}
# 实现IOBbservable,以便将本身注册为可观察
class UserList implements IObservable{
private $_observers = array();

public function addCustomer($name){
foreach($this->_observers as $obs){
$obs->onChanged($this, $name); # 通知观察者
}
}

public function addObserver($observer){
$this->_observers[] = $observer;
}
}

class UserListLogger implements IOBserver{
public function onChanged($sender, $args){
echo("'$args' added to user list\n");
}
}

$ul = new UserList();
$ul->addObserver(new UserListLogger());
$ul->addCustomer("Jack");

Command(命令)

Chain of Respopnsibility(责任链)

包含了一些命令对象和一系列的处理对象,每一个处理对象决定它能处理哪些命令对象,它也知道如何将它不能处理的命令对象传递给该链中的下一个处理对象。

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
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
78
79
80
81
82
83
<?php
abstract class Logger {
const ERR = 3;
const NOTICE = 5;
const DEBUG = 7;

protected $mask;
protected $next; // The next element in the chain of responsibility

public function setNext(Logger $l) {
$this->next = $l;
return $this;
}

abstract public function message($msg, $priority);
}

class DebugLogger extends Logger {
public function __construct($mask) {
$this->mask = $mask;
}

public function message($msg, $priority) {
if ($priority <= $this->mask) {
echo "Writing to debug output: {$msg}\n";
}

if (false == is_null($this->next)) {
$this->next->message($msg, $priority);
}
}
}

class EmailLogger extends Logger {
public function __construct($mask) {
$this->mask = $mask;
}

public function message($msg, $priority) {
if ($priority <= $this->mask) {
echo "Sending via email: {$msg}\n";
}

if (false == is_null($this->next)) {
$this->next->message($msg, $priority);
}
}
}

class StderrLogger extends Logger {
public function __construct($mask) {
$this->mask = $mask;
}

public function message($msg, $priority) {
if ($priority <= $this->mask) {
echo "Writing to stderr: {$msg}\n";
}

if (false == is_null($this->next)) {
$this->next->message($msg, $priority);
}
}
}

class ChainOfResponsibilityExample {
public function __construct() {
// build the chain of responsibility
$l = new DebugLogger(Logger::DEBUG);
$e = new EmailLogger(Logger::NOTICE);
$s = new StderrLogger(Logger::ERR);

$e->setNext($s);
$l->setNext($e);

$l->message("Entering function y.", Logger::DEBUG); // handled by DebugLogger
$l->message("Step1 completed.", Logger::NOTICE); // handled by DebugLogger and EmailLogger
$l->message("An error has occurred.", Logger::ERR); // handled by all three Loggers
}
}

new ChainOfResponsibilityExample();
?>

Iterator(迭代器)

Mediator(中介者)

Memento(备忘录)

State(状态)

Strategy(策略模式)

指对象有某种行为,但是在不同的场景中,该行为有不同的实现算法。

抽象工厂与策略模式不同的地方在于,工厂是创建型模式,关注的是对象的创建,而策略是行为型模式,关注的是对行为的封装,对于工厂来说无论返回的对象内部是怎样的,只要是我想要的对象就行,而对于策略模式来说返回的对象即使不同也会有相同的方法/行为。

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
50
<?php
class StrategyExample {
public function __construct() {
$context = new Context(new ConcreteStrategyA());
$context->execute();

$context = new Context(new ConcreteStrategyB());
$context->execute();

$context = new Context(new ConcreteStrategyC());
$context->execute();
}
}

interface IStrategy {
public function execute();
}

class ConcreteStrategyA implements IStrategy {
public function execute() {
echo "Called ConcreteStrategyA execute method\n";
}
}

class ConcreteStrategyB implements IStrategy {
public function execute() {
echo "Called ConcreteStrategyB execute method\n";
}
}

class ConcreteStrategyC implements IStrategy {
public function execute() {
echo "Called ConcreteStrategyC execute method\n";
}
}

class Context {
var $strategy;

public function __construct(IStrategy $strategy) {
$this->strategy = $strategy;
}

public function execute() {
$this->strategy->execute();
}
}

new StrategyExample;
?>

Visitor(放问者)

TroubleShooting

  • 工厂模式和策略模式的区别
    如大多数网上的解释一样,工厂模式更注重对象的创建,策略模式更注重行为的不同。根据我的理解工厂模式更适用于创建不同的对象,这些对象拥有不同的方法。而策略模式则更多是针对有相同方法的对象。例如网上的文章经常举的例子一样,数据库的操作,其实每个数据库的操作都不一样,所以这里更适用于工厂模式。但是如果有后端一样的场景,那么策略模式就更方便了。例如

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    # 工厂模式
    DBFactory::create(Redis::class)->record()->add();

    # 策略模式(不同的公有云相同的create方法)
    abstract class CloudContext
    {
    protected $cloud;
    public function __construct(Cloud $cloud) {
    $this->cloud = $cloud;
    }
    }
    class HostContext extends CLoudContext
    {
    public function create(
    $this->cloud->create();
    );
    }
    $host new HostContext(CloudFactory::create(AliyunCloud::class));
    $host->create();

安装hexo

1
2
3
curl --silent --location https://rpm.nodesource.com/setup_6.x | bash -
yum install nodejs -y
npm install -g hexo-cli
添加RSS

通过npm install -g hexo-generator-feed安装
然后修改hexo/_config.yml添加如下代码:

1
2
plugins:
- hexo-generator-feed
添加sitemap

通过npm install -g hexo-generator-sitemap安装
然后修改hexo/_config.yml添加如下代码:

1
2
plugins:
- hexo-generator-sitemap

全局设置

文章设置

文章的抬头可以指定如下信息:

1
2
3
4
title: "文章标题"
date: 2016-08-07 8:52:39 # 文章发布时间
updated: 2016-09-02 15:50:00 # 文章修改时间
categories: tools # 文章分类

TroubleShooting

  • nginx 403:可能是因为没有在public生成index.html文件导致,检查是否执行过npm install安装完所有的模块,以及themes目录下是否存在该主题的文件

  • no content页面只有框架没有内容: 可能是历史的css文件遗留所致,此时可以清空public目录下的文件再重新生成

  • unknown block tag: load,出现类似的错误,极大可能是文章中出现了特定的没有转义的标签,例如这里就是很有可能就存在一个类似这种标签,这种标签只能使用代码块(三个单引号)而不能使用两个单引号来引用:

    1
    {% load	# 这样子开头
  • Error: Cannot find module ‘babel-runtime/regenerator’: 原因是没有安装这个包,可以执行npm install babel-runtime --save

每次看到selenium都觉得很牛,但是苦于文档(包括英文)太少,我到今天才真正完整地安装使用了一把。我不喜欢来一个项目就在自己电脑上搭一个运行环境,而是喜欢在docker或者虚拟机里进行操作,问题是docker或者虚拟机里并没有任何的可视化的浏览器,而Selenium又依赖于这些浏览器驱动,我是最讨厌安装驱动的,因为驱动这个东西电脑不同差距特别大,总是会出现各种问题。而在服务器上如何安装selenium或者splinter,这个过程在网上基本是找不到的,所以这里记录下自己的安装方法。

注:这里之所以要使用splinter,而不只使用selenium是因为splinterselenium之上又封装了一层,使得接口更为简单。另外,即使是用的splinter,也可以通过browser.driver直接获取到selenium的驱动的。

Linux install Splinter(Selenium)

首先,需要安装必要的python包pip3 install splinter selenium xvfbwrapper需要注意的是,splinter只有在使用浏览器的时候才需要安装selenium,如果仅仅是在flask或者django中进行测试是不需要的。

安装chromedriver

ChromeDriver首页-WebDriver for Chrome,下载对应操作系统的最新的chromedriver

1
2
3
4
5
wget http://chromedriver.storage.googleapis.com/2.23/chromedriver_linux64.zip
unzip chromedriver_linux64.zip
mv chromedriver /usr/bin # 添加到PATH即可

chromedriver # 运行命令进行测试,没抛错则表示正确了
Docker版本Selenium

https://github.com/SeleniumHQ/docker-selenium

handless browser

https://zhuanlan.zhihu.com/p/26810049?utm_medium=social&utm_source=qq

Linux Server(Raspberry Pi)安装浏览器

上面的方式是直接打开浏览器的方式,但是在Server上面没有界面,也就没有浏览器,这种情况就得安装单独的真对server的浏览器了。最先我想使用ChromeDriver,但是无论怎么折腾也安装不上,于是就用了Firefox,发现一篇很好的教程。它这个版本被称作Selenium headless firefox。安装步骤如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 添加repository,并安装firefox
sudo add-apt-repository ppa:mozillateam/firefox-stable
sudo apt-get update
sudo apt-get install firefox

# 安装Xvfb: 是用来虚拟X服务程序,实现X11显示的协议
sudo apt-get install xvfb
sudo Xvfb :10 -ac # 10表示编号

# 设置环境变量
export DISPLAY=:10

# 就可以使用了
firefox

开始Splinter(Selenium)

无桌面环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from splinter import Browser
from xvfbwrapper import Xvfb
from selenium.webdriver.chrome.options import Options

# 由于是在server上运行chrome,所以必须用一些模拟器
vdisplay = Xvfb()
vdisplay.start()

# 这些设置都是必要的
chrome_options = Options()
chrome_options.add_argument("--no-sandbox")
chrome_options.add_argument("--disable-setuid-sandbox")
chrome_options.add_argument("--ignore-certificate-errors") // 忽略网页的证书错误

# 这里才是正式的使用了
browser = Browser('chrome', options=chrome_options, executable_path='/root/bin/chromedriver')
browser.visit('https://haofly.net')
print(browser.title)

browser.quit()
vdisplay.stop()

桌面环境

如果直接在本地有桌面环境的情况下进行测试那么,直接这样子:

1
2
3
4
5
6
7
8
from splinter import Browser
from selenium.webdriver.chrome.options import Options

chrome_options = Options()
browser = Browser('chrome', executable_path='/Users/haofly/share/chromedriver', user_agent='User-Agent设置', options=chrome_options)
browser.driver.set_window_size(1500, 900) # 设置浏览器的size
browser.visit('https://wiki.haofly.net')
print(browser.title)

Options选项

通过chrome_options.add_argument('')可以设置非常多的浏览器的参数

1
2
3
disable-infobars	// 禁用网页上部的提示栏,比如2.28的webdriver开始会提示你Chrome正受到自动测试软件的控制,这个特性应该是chrome为了安全给加的

option.add_argument('--user-data-dir=/path') # 指定个人资料路径,指向自己的浏览器,这样,chromedriver的配置就能和自己的一样了

获取所有网络请求

很多时候访问一个页面,在该页面可能会同时访问其他的资源,例如js,css,甚至其他一些关键信息。这时候就要求我们能够获取中间的所有的请求,但是selenium是不带这个功能的,只能使用一些代理,例如:browsermob-proxy。其不需要安装,只需要release下载bin包,然后pip install browsermob-proxy,然后在使用的时候指定路径即可。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from browsermobproxy import Server
server = Server("~/browsermob-proxy-2.1.4/bin/browsermob-proxy")
server.start()
proxy = server.create_proxy()

chrome_options = Options()
chrome_options.add_argument('--proxy-server={host}:{port}'.format(host='localhost', port=proxy.port))
browser = Browser('chrome', executable_path='~/share/chromedriver2.28', options=chrome_options)
browser.driver.set_window_size(1500, 900) # 设置浏览器的size
browser.driver.set_page_load_timeout(1) # 设置页面加载超时时间,防止visit一直卡住

proxy.new_har()
browser.visit('https://haofly.net')
print(proxy.har) # 以json的形式打印出中间所有的网络请求

浏览器操作

1
2
3
4
5
6
7
8
9
10
browser.windows	# 所有打开了的窗口
browser.windows[0] # 第一个窗口
browser.windows.current # 当前窗口
browser.windows.current = browser.windows[2] # 切换窗口
browser.window[0].close() # 关闭窗口
browser.window[0].close_others() # 关闭其他窗口
browser.window[0].is_current # 序号为零的窗口是否是当前的窗口
browser.window[0].next # 下一个窗口
browser.window[0].prev # 上一个窗口
browser.quit() # 退出浏览器

页面操作

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
browser.visit(url)	# 访问URL
browser.reload() # 重新加载当前页
browser.back() # 回退
browser.forward() # 向前

browser.find_by_tag('h1').mouse_over() # 鼠标移动到某个元素上
browser.find_by_tag('h1').mouse_out() # 鼠标移开
browser.find_by_tag('h1').click() # 鼠标点击事件
browser.find_ty_tag('h1').double_click()# 鼠标双击事件
browser.find_by_tag('h1').right_click() # 鼠标右键点击

draggable = browser.find_by_tag('h1') # 鼠标拖曳事件
target = browser.find_by_css('.container')
draggable.drag_and_drop(target)

# 点击链接
browser.click_link_by_href('http://www.the_site.com/my_link')
browser.click_link_by_partial_href('my_link')
browser.click_link_by_text("submit")
browser.click_link_by_partial_text('part of link text')
browser.click_link_by_id('link_id')

# 点击按钮
browser.find_by_name('send').first.click()
browser.find_link_by_text('my link').first.click()

# 表单填写
browser.fill('query', 'my name') # 直接通过name属性的元素输入
browser.find_by_tag('input').first.fill('my name')
browser.attach_file('file', '/path/to/file/somefile.jpg')
browser.choose('some-radio', 'radio-value')
browser.check('check-name') # checkbox
browser.choose('name', 'value') # radio
browser.uncheck('some-check')
browser.select('uf', 'rj')

# 元素基本操作方法
ele.click() # 点击元素
ele.fill('value') # 输入字符
ele.has_class('name') # 是否包含某个类
ele.mouse_out()
ele.mouse_over()
ele.screenshot()
ele.select('option', slowly=False) # 下拉选择option
ele.text # 元素内部的文字内容
ele.type('value', slowly=False)
ele.uncheck()
ele.value # 跟.text差不多
ele.clear() # 清空元素内容

# selenium发送按键
browser.driver.find_element_by_id('title').send_keys(Keys.BACKSPACE)

数据获取

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
browser.title	# 获取网页title
browser.html # 获取网页内容
browser.url # 获取网页url
browser.find_by_css('h1').first.value # 获取元素值

browser.is_text_present('', wait_time=None) # html里面是否存在某个字符串

# 查找css元素
browser.find_by_css('h1')
browser.find_by_xpath('//h1')
browser.find_by_tag('h1')
browser.find_by_name('name')
browser.find_by_text('Hello World!')[1]
browser.find_by_id('firstheader').last
browser.find_by_value('query').first
browser.driver.find_element_by_name('A').find_element_by_xpath('..') # 利用selenium原生来实现寻找parent父级元素

# 寻找网页链接
browser.find_link_by_text()
browser.find_link_by_partial_text()
browser.find_link_by_href()
browser.find_link_by_partial_href()

# 可以连续用的
browser.find_by_tag('div').first.find_by_name('name')

# 获取Alert/Prompt
alert = browser.get_alert(wait_time=None) # 获取alert,官方文档说获取不到返回为None,但是实际会报超时错误

TroubleShooting

  • Chrome driver crashes when opens a new tab

    原因可能是服务器内存太低了,需要加大虚拟内存

  • selenium.common.exceptions.WebDriverException: Message: session not created exception,将webdriver更新到最新版基本上能解决问题

  • session not created: This version of ChromeDriver only supports Chrome version 77: 这是因为下载的chromedriver版本和你当前系统已经安装的chrome版本不一致造成的,需要对其中某一个进行升级或降级

  • Unable to connect host: 如果浏览器能正常访问,那么可能是chromedriver打开网页有问题,导致splinter未去访问目标网页,并不是网络连接问题

  • browsermob-proxy 地址已在使用中: 可以指定代理服务器的端口:

    1
    2
    3
    4
    5
    from browsermobproxy import Server
    dict={'port':8090}
    server = Server(path="./browsermob-proxy",options=dict)
    server.start()
    proxy = server.create_proxy()
相关文章

Getting Started with Headless Chrome

Setting up a Digital Ocean server for Selenium, Chrome, and Python

PHP的单元测试工具比Python好选择,基本上就是PHPUnit了。

断言

1
$this->setExpectedException(Exception class);	// 断言将会出现某个exception	

Mockery mock对象

Mockery默认包含在PHPUnit中,根据我的经验,学会了mock才算真正学会了单元测试,无论是Python还是PHP都是如此。使用它也解决了我之前以为依赖注入对测试来说极大地加大了复杂度的错误思想。使用方法如下:

1
2
3
4
5
6
7
8
# 首先在一个测试类中
$this->object = Mockery:mock('class');

# 模拟方法的返回值
$this->object->shouldReceive('getValue')->andReturn('abc');
$this->object->getValue() # 这样就会返回abc
$this->object->shouldReceive('getAttribute')->with('value')->andReturn('def');
$this->value # 这样会返回属性的值

Xdebug的使用

既然用上了单元测试,那为何不继续深入使用,添加代码覆盖率的测试,以前我认为覆盖率只是用来给我们玩玩儿的,当我真正开始写了后才知道,真的太有用了。首先,他让我了解到一个真相,那就是我自己写的代码,很多地方,我自己都不清楚,有些异常我根本不知道在什么情况下会抛,有好多重复的地方代码根本不应该存在,有好多逻辑没考虑完整。要查看代码覆盖率还需要安装Xdebug。

安装方式:

1
2
3
4
sudo apt-get install php-pear php5-dev libcurl3-openssl-dev -y
pecl install xdebug
# 然后分别在/etc/php5/cli/php.ini和/etc/php5/apache2/php.ini 里面分别添加
zend_extension="/usr/local/php/modules/xdebug.so"

使用:

phpunit testfile.php --coverage-html foldername即可生成html文件

TroubleShooting

  • Call to a member funciton make() on null错误

    只需要添加父类构造方法即可:

    1
    2
    3
    function __construct(){
    parent::setUp();
    }

前几天,一位朋友突然问我道,为什么我的博客最近更新的都是有关读后感的,而少了以前那么多的技术干货。很欣慰,居然有朋友一直在看我的站点,虽然并没有看到它想看的,但是他所要的技术干货我现在其实也有,只是并没有放在主站,而是放在豪翔天下的wiki这个二级域名下。至于为什么,我想是因为厌倦了很多博客的内容样式,厌倦了很多教程的复制粘贴,才有了把它们一分为二的想法。

看了一下百度统计和谷歌Analytics,博客从13年至今,统计在内的PV总共有20000+,单日最高PV居然有1300+,但是平日里也就10左右。虽然我有用统计工具,但是几乎不会去关注这些统计结果,一直以来我写博客都是为了自己在写,即使是技术干货,也是为了方便自己以后查阅。但是最近又有了一些新的方法,我认为,我现在可以宣传自己了。以前的我很自卑,觉得自己写的东西,那肯定都是别人写过的,而且自己写的东西更是没有多少技术深度的东西,但是事实好像并不是这样。我最骄傲的地方就是与身边的不一样,我每天都在浏览着很多科技博客、技术新闻,以为自己一直是站在技术前沿的人,也意识到自己学的东西范围非常广,自己的深度也不够,眼前有很多路,却不知道该往哪里走。这就像一个瓶子,明明上面就是瓶颈了,但就是怎么做也钻不出去,甚至在往下掉。
到新公司也快半年了,新同事们很好,也很牛逼,感觉大家简直就像游侠一样,不想去北上广争夺,只想待在重庆这个小江湖,说实话,他们的技能水平,在北上广绝对也能称得上高手。这半年里,因为有人在旁边指导,在代码质量方面成长得挺快,至少现在写代码的时候能明显感觉到自己以前写的代码是有多么的bad smell。在给别人开发的时候也感觉自己的代码量真的太少了。以前都是自己开发自己的东西,觉得有时候有点错误没什么,但是给别人开发的过程中,一点儿不满意人家就会指出来,而且让人伤心的是,这里面起码有百分之五十确实是自己代码质量的问题。路漫漫其修远兮,编程之路,我还有很长的路要走。

前几天翻了以前的博客,我去,居然没有今年的年初计划,不禁思考,我这半年是怎么浑浑噩噩过来的,接下来的半年又该怎样浑浑噩噩地过呢。浑浑噩噩这个词我不喜欢,你说我懒我会接受,我懒得开心,但是说我浑浑噩噩,说我不思进取,那肯定是不行的。在这里,补一下下半年的目标:

  1. 熟练日语五十音(说了很多次要学日语,结果到现在连五十音都还不认识,要想装逼,至少要会说吧)
  2. 结交三个以上IT界朋友(年龄相仿+忘年)
  3. 博客日访问量10+(我承认,现在的博客日访问量还有零的)
  4. 年底公司评级达到P4及以上(这就要求在公司好好工作啦)
  5. 给父母做全身检查(哪怕再缺钱也得做)
  6. SegmentFault声望达到1000以上

常用操作

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
# 字符判断
string.isalpha() # 是否为字母
string.isdigit() # 是否为数字
string.isalnum() # 是否为数字或字幕
string.islower() # 是否都是小写
string.isupper() # 是否都是大写
string.istitle() # 是否都首字母大写
string.isspace() # 是否都是空白字符

# 格式化字符串
'abcdef %s' % (123) # 特别适合长字符串不用加号来拼接字符串的情况
"my name is {name}".format(name=name) # 这种方式好处是可以直接定义名字
"my name is {} at {}".format(name, address) # 这种方式可以在少数变量的情况下偷一下懒
"{1} {0} {1}".format("hello", "world") # 可以指定位置
f'my name is {变量名}' # Python3.6里面新增的特性,可用这种方式直接格式化字符串
f'{name=}' # python3.8开始直接这样可以输出name=value形式的字符串
f'{{}}' # 直接插入大括号
my_dict = {'name': '...', 'addr': '...'}
'my name is {name} at {}'.format(**my_dict) # 直接解析字典的参数
my_list = {'name', 'addr'}
'my name is {0[0]} at {0[1]}'.format(my_list) # 直接解析列表的参数
''.join(['a','b']) # 合并字符串列表并增加分隔符,需要注意的是列表中必须为字符串,如果是数字需要强制转换一下

# 格式化数字
'{:.2f}'.format(3.1415926) # 3.14, 保留两位小数

# 指定列宽格式化字符串
import textwrap
print(textwrap,fill(s, 70)) # 将s字符串已70列显示,多的换行
os.get_terminal_size().columns # 可以使用这个方法获得终端的大小和尺寸

# 字符串填充,数字前补零
a = 'abc'
print(a.zfill(5)) # 输出'00abc'

# 去掉空格
s.strip() # 去掉两端空白
s.lstrip() # 去掉左边空白,加参数可以
s.rstrip() # 去掉右边空白
s.replace(' ', '') # 去掉所有空白
s.removeprefix('.png') # py3.9,删除后缀
s.removesuffix('abc') # py3.9,删除前缀

# 大小写转换
s.upper() # 全部转为大写
s.lower() # 全部转为小写

# json格式去掉冒号后的空格
json.dumps(string, separators=(',', ':')) # 默认的分隔符是(', ', ': ')
# json格式输出中文而不是unicode字符串
json.dumps(string, ensure_ascii=False)

# url编码与解码
from urllib import parse
parse.unquote(url) # url解码
result = parse.urlparse(url) # url解析
query_dict = parse.parse_qs(request.query) # 获取查询参数
query_dict.get('field', []) # 获取指定参数

# 进制转换
binascii.b2a_hex(string.encode('utf-8')) # 字符串转16进制

''.join(random.sample(string.ascii_letters + string.digits, 10)) # 生成随机字符串

查找与替换

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
# startswith
str.startswith(str, beg=0, end=len(string))
str.endswith(str)

# 统计子字符串出现次数
str.count('sub_str')

# 字符串替换
import re
text = 'Today is 03/16/2023'
pat = re.compile(r'(\d+)/(\d+)/(\d+)')
pat.sub(r'\3-\1-\2', text)
'Today is 2016-11-27'

>>> text = 'Today is 03/16/2023, tomorrow is 03/17/2023'
>>> re.sub(r'(\d+)/(\d+)/(\d+)', r'\3-\1-\2', text)
'Today is 2023-03-16, tomorrow is 2023-03-17'

# 带命名组的替换
re.sub(r'<a.*?>(.*?)</a>','\g<1>', text) # 替换a标签,但保留a标签里面的内容,需要注意的是.*表示最长匹配,而.*?表示最短匹配。添加参数flag=re.IGNORECASE表示不区分大小写

# 对替换做特殊处理
print(re.sub('(?P<value>\d+)', lambda matched: str(int(matched.group('value')) * 2), s))

# 使用正则方式查找
import re
url = 'http://haofly.net/note.html'
match = re.search('(.*)/(.*?).html', a)
match = re.search('(.*)/(.*?).html', a, re.IGNORECASE) # 不区分大小写的正则匹配
print(match.group(1), match.group(2))

# 基本查找,返回第一个出现该字符串的位置
text.find(',')

# 查找某字符串出现的所有位置的一个列表
[m.start() for m in re.finditer(',', text)] # 输出[4, 11]

# 忽略大小写的查找
re.findall('python', 'text, flags=re.IGNORECASE)

# 查找所有匹配的
matches = re.findall('pattern', string) # 返回所有匹配的列表

# 最短匹配
str_pat = re.compile(r'\\"(.*?)\\"')
str_pat.findall(text2) # 输出['no.', 'yes.']

# 查找时中文编码问题
re.search('中文(.*?)呵呵'.decode('utf8'), string)

# 字符串分割-正则方式
line = 'asdf fjdk; afed, fjek, asdf, foo'
import re
re.split(r'[;,\\s]\\s_', line) # ['asdf', 'fjdk', 'afed', 'fjek', 'asdf', 'foo']
re.split(r'(;|,|\\s)\\s_', line) # 这样连分隔符都能分割出来
re.split('(==|>=|<=|>|<)', 'requests>1.2.3') # 多个字符的解析

时间处理

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
# 简单的获取时间:
import datetime
a = str(datetime.date.today())
print(a) # 格式为2015-07-17
a = time.strftime('%H:%M:%S') # 格式为11:20:00
time.strftime('%Y-%m-%d %H:%M:%S')

# 获取当前时间时间戳
time.time()

# 字符串转时间:
time_str='Tue, 11 Nov 2014 06:37:20 +0000'
date = datetime.datetime.strptime(time_str, '\%a, \%d \%b \%Y \%H:\%M:\%S \%z')
print(date) # 输出'2014-11-11 06:37:20+00:00'
print(date.timestamp()) # 输出时间戳'1415687840.0'
# 或者
date = datetime.datetime(2006, 12, 12, 12, 12, 12)

# 获取当天开始和结束的时间(即00:00:00到23:59:59)
today = datetime.date.today()
datetime.datetime.combine(today, datetime.time.min)
# 得到datetime.datetime(2015, 7, 24, 0, 0)
datetime.datetime.combine(today, datetime.time.max)
# 得到datetime.datetime(2015, 7, 24, 23, 59, 59, 999999)

# 时间加一天,加一分钟,昨天,明天,前面几天,后面几天
now = datetime.datetime.now()
date = now + datetime.timedelta(days = 1)
date = now + datetime.timedelta(seconds = 3)

# 关于时间占位符总结:
%d:日
%b:简写的月份,如Oct
%Y:年份
%H:小时
%m:月
%M:分钟
%S:秒
%z:与时区相关,在标准时间上加时间,例如'+00:00'

# 各种格式举例
time.strftime('%Y-%m-%dT%H:%M:%S%z') # 2015-11-11T02:49:03+00:00

# 转换时间为UTC/GMT时间
time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(time.mktime(time.strptime("2008-09-17 14:04:00", "%Y-%m-%d %H:%M:%S"))))

# 时间戳的转换:
ltime=time.localtime(1395025933)
timeStr=time.strftime("\%Y-\%m-\%d \%H:\%M:\%S", ltime)

string = '2015年09月18日 00:01:00'
date = time.strptime(string, '\%Y年\%m月\%d日 \%H:\%M:\%S')
b = time.mktime(date) # 获取时间戳

# datetime转时间戳
time.mktime(the_date.timetuple())

# 时间戳转datetime
datetime.datetime.fromtimestamp(1234567890)

# 获取本月有多少天,以及最后一天的计算方法
import calendar
today = datetime.date.today()
_, last_day_num = calendar.monthrange(today.year, today.month)
last_day = datetime.date(today.year, today.month, last_day_num)

# 计算间隔时间
begin = datetime.datetime(2015, 3, 14, 23, 59, 59)
today = datetime.datetime.today()
interval = today - begin
interval.seconds() # 时间差多少秒
interval.days # 相差多少天,对应的.seconds表示相差多少秒,小时等同理

编码问题

  • 2中打印str显示前面加了个u且中文类似\u8be5\u9879:这是十六进制的Unicode编码,使用string.encode('utf-8')进行转换

  • 2中类似\uiahd\u9483这样的字符串:需要注意的是,该字符串本来就是这样,而不是编码成这样的,这时候需要反编码:string.decode('unicode_escape'))

  • 2中无法输出中文: 无论是str还是unicode还是encode('utf-8')都无法输出中文,可以试试在print的时候不带括号: print a,但是print一个对象依然不行,可以单独打印某个字段

  • 无法解析\u2c这样的unicode字符,出现错误UnicodeDecodeError: 'unicodeescape' codec can't decode bytes in position 0-3:truncated \uXXXX escape: 原因是unicode默认是\uxxxx这样的形式来解析字符串的,但是如果出现\u2c这种,是解析不了的,应该写成\u002c这种形式,前面需要补全

  • UnicodeDecodeError: ‘ascii’ codec can’t decode byte: 可以试试unicode(string, 'utf-8)

  • url编码 Python3中,url编码放在了url lib.parse中了

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # python3
    from urllib import parse
    parse.quote(str) # urlencode
    parse.quote_plus(str)
    parse.unquote(str) # urldecode
    parse.encode() # 把字典转换为query的方式

    # python2
    urllib.urlencode(dict)
    urllib.quote(str)
    urllib.unquote(str) # urldecode
  • bytes to string/字节转字符串

    1
    b"abcde".decode('utf-8')
  • string to bytes/字符串转字节

    1
    str.encode("abcfc")
  • 将字符串输出为16进制字节:

    1
    2
    3
    4
    ":".join("\{:02x\}".format(ord(x) for x in 字符串))
    # 或
    ":".join("\{0:x\}".format(ord(x) for x in 字符串))
    # 输出类似于: 12:45:45
  • 16进制转换为utf-8 :类似 \xe5\x94\xae\这种,使用如下方式进行转换

    1
    2
    3
    4
    5
    6
    7
    # 方法一
    unicode(string, 'utf-8')

    # 方法二
    a = u'xb3\xe5'
    b = array('u', a).tostring()[::2].decode('gbk')
    print(b)
  • base64编码和解码

    1
    2
    3
    import base64
    a = base64.b64encode(s)
    b = base64.b64decode(a)
  • gb2312字符串转换为utf-8

    1
    data.encode('latin1').decode('gb2312')
  • 大端/小端

    Python使用struct.pack和struct.unpack来将数据封装成大端/小端的字节流,例如struct.pack('>h',14)表示将14封装成大端模式

  • 查看字符编码

    1
    2
    import chardet
    chardet.detect(string)

TroubleShooting

  • “TypeError: Unicode-objects must be encoded before hashing”

    原因是在3.x中,md5方法仅接受unicode编码过后的字符串:

    1
    hashlib.md5(string.encode('utf-8')).hexdigest()

Github Tools

rclone

云存储命令行工具,支持Google Drive, Amazon Drive, S3, Dropbox, Backblaze B2, One Drive, Swift Hubic, Cloudfiles, Google Cloud Storage, Yandex FIles

Python

如果要使用pip安装最新版本可以在后面加上版本号

backoff

一个支持代码重试机制的装饰器

BeautifulSoup4

XML/HTML解析组件

better-exceptions

能够将异常打印得非常直观好看,并且能显示某些具体的值

coverage

代码覆盖率检测工具

django.contrib.syndication.views

Django自带的输出feed的工具

django.core.paginator

这是Django自带的分页工具,非常实用

django-avatar

Django头像插件

django-extensions

Django的扩展包的包,带有非常方便的一些工具,比如自动打印sql语句等。

django-debug-toolbar

Django的调试工具集,包含了很多的调试及性能优化工具,应该非常好用,未使用过

django-haystack

Django的全文搜索功能

django-redis

在Django中使用Redis必备。需要注意的是,它对value做了序列化,而且在key前面加入了版本号,类似_:1:key,_而且,默认生存时间是300秒,需要加入参数_cache.set(“key”, “value”, timeout=None)_。Redis密码的格式应该是 “LOCATION”: “redis://:密码内容@104.236.170.169:6379/1″,真的服了官网那不明不白的表述了

django-rest-framework

Django的Restful框架

django-social-auth

Django社会化认证工具

django-socketio

Django的WebSockets ,好爽

django-wysiwyg

Django使用wysiwyg作为富文本编辑器

dh-virtualenv

Python部署工具,弃用pip,而是将package打包成Debian packages的形式,自动解决各种依赖问题

difflib

Python自带模块,比较文本之间的差异,且支持输出可读性强的HTML文档

dpart

Spark的Python实现,分布式任务处理

fuzzywuzzy

计算字符串相似率

hashids:

将整数转换为hash值,并且支持反解,这不仅仅是Pythond的一个库,而且支持几十种语言。可用于将后台生成的唯一ID转换成混淆的hash值。

httpstat:

在命令行打印CURL请求的详细信息

IPy

IP地址处理模块,可用于计算大量的IP地址,包括IPv4、IPv6网段、网络掩码、广播地址、子网数、IP类型等。参考文章

jieba(官方文档)

结巴中文分词,未使用过

lunardate

获取农历

memory_profiler

能够分析每行代码每个变量的内存使用量,用于优化效率

MkDocs

项目文档工具,以markdown的方式攥写spinx烈性的文档

**MoviePy**:Python处理视频文件

MRQ: Python的分布式worker任务队列,使用Redis和gevent。既有RQ那样简单,又有Celery的性能。,具有强大的用户面板,可以控制队列中的任务、当前任务、workder的状态,并且能按任务区分日志。

mysqlclient: Python3链接MySQL/Mariadb数据库的库,相比于官方的库以及众多其他第三方库,这个库虽然只有一个人在维护开发,但是Pypi的权重值有9,而且Github一直有更新。在安装的时候需要先安装依赖:sudo apt-get install python-dev libmysqlclient-dev,Python3要加3,windows下可以直接安装,如果是OS X,那么可能是没有将mysql添加到环境变量,在_.profile_做如下修改

1
2
3
4
PATH="/Library/Frameworks/Python.framework/Versions/3.5/bin:/usr/local/bin/python3:/usr/local/mysql/bin:${PATH}"
export PATH
DYLD_LIBRARY_PATH="/usr/local/mysql/lib/${DYLD_LIBRARY_PATH}"
export DYLD_LIBRARY_PATH
paramiko(官方文档)

基于Python2/3实现的SSH2的库,支持认证及密钥方式,可以实现远程命令执行、文件传输、中间SSH代理等功能。windows安装的时候会有依赖问题,可见这个issue

pep8

PEP8规范检测工具,使用时直接pep8 ./

Pillow

Python图像处理库,与PyLab互斥,只能安装一个哟

progressBar2

在终端显示进度条

psutil

跨平台的获取系统运行的进程和系统利用率(包括CPU、内存、磁盘、网络等)信息的库,主要用于系统监控,分析和限制系统资源及进程的管理。实现了一些命令行的工具(如:ps、top、lsof、netstat、ifconfig、who、df、kill、free、nice、ionice、iostat、iotop、uptime、pidof、tty、taskset、pmap等)

pyautogui

跨平台的python自动化模拟输入模块,能够模拟鼠标和键盘

pyspider

有图形界面的爬虫程序

python-nmap

使用Python实现的端口扫描工具

random-avatar

直接生成指定大小的随机头像,是按照你的IP来计算的

requests

(官方文档),比SSL和HttpResponse更加高级,更方便,一句话就可以搞定人家几十句的功能,非常方便

SaltStack

基于Python开发的一套C/S架构配置管理工具,底层使用ZeroMQ消息队列pub/sub方式通信,使用SSL整数签发的方式进行认证管理。而Ansible基于ssh协议传输数据,所以SaltStack普遍被认为比Puppet快,缺点是需要安装客户端。

SciPy

Python科学计算库

stackoverflow

直接通过关键字从stackoverflow上面抓去来作为一个工具函数,黑科技

xpinyin

汉字拼音

Python-GUI

Camelot
Cocoa
GTk
Kivy

跨平台,完全免费

PyObjC

仅仅OS X可用,但是也非常方便

PyQT

跨平台,但商业使用需要商业许可证

PHP

clockwork

可以直接在浏览器里面查看性能的性能调试工具(有个坑是如果你用的是其他会修改route规则的插件,那么必须保证能访问/__clockwork才能使用)

config

轻量级的配置文件读取工具,支持PHP/INI/XML/JSON/YAML文件

guzzle

requests更好用的请求库,已经放弃requests库了,更新很慢,无法上传文件,目测作者也已经放弃这个库了,已经没有回复PR了。。。

jsonmapper

自动将JSON对象转换为相应的类对象,相当于Java里面的bean

PhpSms

可能是目前最聪明、优雅的php短信发送哭了。从此不再为各种原因造成的个别发送失败而烦忧。。。。

Go

logrus

比自带的log好用得多的日志库

Java

retrofit

Java里面非常好用的HTTP client,用起来显得十分简洁,简化了HTTP请求

JS/Jquery

Awesomplete:jQuery的联想次插件,必须异步加载哟,例如:
1
2
3
4
5
6
7
8
<script type="text/javascript">
$(function(){
var input = document.getElementById("myinput");
var awesomplete = new Awesomplete(input);
awesomplete.list = ["Ada", "Java", "JavaScript", "Brainfuck", "LOLCODE","Node.js" , "Ruby on Rails"];

});
</script>
BootSideMenu

Bootstrap隐藏滑动侧边栏jQuery插件,虽然不大好看,依赖还有点多,不过好用

bootstrap-select

基于Bootstrap和jQuery的下拉选择输入列表插件

clipboard.js

纯HTML5实现的复制到粘贴板的插件

DataTables

表格插件,几乎涵盖了所有想要的功能,定制化非常强

editor

一个十分漂亮的markdown编辑器

fingerprintjs

浏览器唯一性解决方案

fullpage.js

全屏插件

lightslider

图片平滑滚动插件

hotkeys

无任何依赖的键盘事件捕获插件

jquery-notebook

简洁的网页编辑器

simditor

彩程设计的wysiwyg类型的编辑器

Smoothzoom

简单的图片点击放大组件

three.js

有太多酷炫的效果了(webgl)

unslider

用过最好用的图片轮播插件,而且用起来也特简单

wysihtml

十分强大的网页编辑器,但是文档几乎没有,上面有Django版本

PHP

Carbon

各种时间处理

laravel-5-markdown-editor

Laravel5 Markdown编辑器

PHP Debug Bar

方便调试,可以直接在浏览器里面看到变量信息,而不用var_dump()了

python requests模块

一直以来我都看不惯python自带的urllib包的繁琐的使用方法,所以我都使用的requests包来代替原生方法。它能方便的发送GET和POST请求,支持HTTPS,基本上能模拟人类真实的访问。

发送请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 动态method
requests.request(method, url, ...)

# GET请求
response = requests.get(url, params={}, timeout=5, allow_redirects=False) # allow_redirects表示是否允许重定向,默认为True
r = requests

# POST请求
r = requests.post(url, data=参数字典)

# json请求,需要注意的是,如果data不显示设置为json数据,那么服务端收到的是application/x-www-form-urlencoded格式,无论你是否设置header头
r = requests.post(url, json=data) # 这样可以直接将字典作为参数
r = requests.post(url, data=json.dumps(data), headers={'content-type': 'application/json'})
r = json() # 获取json响应

r = requests.post(url, verify=False) # 禁用https的验证

设置请求

1
2
requests.get(url, timeout=30)	# 设置连接超时时间为多少秒,不是响应时间
requests.get(url, timeout=(10, 30)) # 第一个是连接时间,第二个是等待响应的时间

自定义HTTP头

1
2
3
4
headers = {
'User-Agent': '注意名称'
}
requests.get(url, headers=headers)

会话对象

以这种方式可以跨请求保持某些参数,不用再自己提取上一次请求的信息了,比如cookie等,但是需要注意的是,即使使用了会话,方法级别的参数并不会跨请求保持,如果要跨方法,可以使用with

1
2
3
4
5
6
s = requests.Session()
s.get(url)
r = s.get(next_url)

with requests.Session() as s:
s.get(url)

设置代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# HTTP代理
proxies = {
"http": "http://127.0.0.1:8118",
"https": "http://127.0.0.1:8118",
}
# socks代理
## 安装依赖pip install requests[socks]
proxies = {
'http': 'socks5://user:pass@host:port',
'https': 'socks5://user:pass@host:port'
}

requests.get("http://google.com", proxies=proxies)


上传文件

类似这样的post请求,就是把文件和要发送的字段一起发送上去的

1
2
3
4
5
6
7
8
9
10
11
12
13
-----------------------------241652170216373
Content-Disposition: form-data; name="value_1"

12345
-----------------------------241652170216373
Content-Disposition: form-data; name="value_2"

67890

--273f13699c02429db4eb95c97f757d38
Content-Disposition: form-data; name="value_2"; filename="value_2"

67890

上传方式

1
2
3
4
5
r = S.post(url, files={
'file_0': open('filename', 'rb'), # 这里的字段名一般都取file_0,然后顺序下去
'file_1': open('filename2', 'rb'),
'其他字段': (None, '字段值')
})

获取响应

1
2
3
4
5
6
7
8
response.text	# 获取编码后的响应文本信息
response.json() # 获取json响应
response.content # 直接获取响应的二进制信息
response.cookies # 获取响应cookie对象
response.status_code # HTTP status,http状态码
response.raise_for_status() # 当响应出错,即为4xx或者5xx的时候直接抛出requests.RequestException错误
response.encoding # 获取网页的编码方式,需要注意的是,requests参照的严格http协议标准写的,如果响应中的Content-Type字段没有设置charset,那么即使网页标签中有明确是utf-8编码也是会自动设置成ISO-8859-1编码的。此时只需要下面这样修改编码即可
response.encoding = 'utf-8' # 直接将response设置为指定的编码,最好只针对ISO-8859-1进行这样转换,因为其他有声明编码的用这种方式可能反而获取不到正确的值

重定向

1
2
# 如果直接请求,如果发生重定向,那么response.status_code = 200, response.history = 301
print([x for x,y in A.__dict__.items() if type(y) == FunctionType])

下载文件

1
2
3
4
5
6
7
8
# 小文件,直接吸入
open('filename', 'wb').write(r.content)

# 大文件,分片写入
r = requests.get('https://haofly.net/test.mp4', stream=True)
with open('filename.mp4', 'wb') as fd:
for chunk in r.iter_content(1024):
fd.write(chunk)

TroubleShooting

  • 设置最大重试次数
    之前发现设置了timeout时间却没反应,原来是因为查询不到ip地址,导致在timeout时间内就已经默认在重试了,要设置就得先执行语句requests.adapters.DEFAULT_RETRIES=5

  • 无法获取httponly的cookie信息
    通过headers.get('Set-Cookie')获取cookies字符串,然后通过正则匹配来查找

  • SSLError(SSLEOFError(8, ‘EOF occurred in violation of protocol (_ssl.c:1038): 检查代理ssl的代理设置是否正确,可以取消全局代理,直接用proxies参数设置代理试试

  • 发送https请求出现警告: Suppress InsecureRequestWarning: Unverified HTTPS request is being made

    这是因为在发送https的时候未使用证书进行认证,如果一定要关闭这个警告添加这样的语句:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # ways 1
    import requests
    from requests.packages.urllib3.exceptions import InsecureRequestWarning

    requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

    # ways 2
    import urllib3
    from urllib3.exceptions import InsecureRequestWarning
    urllib3.disable_warnings(InsecureRequestWarning)
  • **user-agent列表**

    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
    user_agent_list = [  
        'Mozilla/5.0 (Linux; Android 4.1.1; Nexus 7 Build/JRO03D) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Safari/535.19',
        'Mozilla/5.0 (Linux; U; Android 4.0.4; en-gb; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',
        'Mozilla/5.0 (Linux; U; Android 2.2; en-gb; GT-P1000 Build/FROYO) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1',
        'Mozilla/5.0 (Android; Mobile; rv:14.0) Gecko/14.0 Firefox/14.0',
        'Mozilla/5.0 (Android; Tablet; rv:14.0) Gecko/14.0 Firefox/14.0',
        'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:21.0) Gecko/20100101 Firefox/21.0',
        'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:21.0) Gecko/20130331 Firefox/21.0',
        'Mozilla/5.0 (Windows NT 6.2; WOW64; rv:21.0) Gecko/20100101 Firefox/21.0',
        'Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19',
        'Mozilla/5.0 (Linux; Android 4.1.2; Nexus 7 Build/JZ054K) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Safari/535.19',
        'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36',
        'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.11 (KHTML, like Gecko) Ubuntu/11.10 Chromium/27.0.1453.93 Chrome/27.0.1453.93 Safari/537.36',
        'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.94 Safari/537.36',
        'Mozilla/5.0 (iPhone; CPU iPhone OS 6_1_4 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) CriOS/27.0.1453.10 Mobile/10B350 Safari/8536.25',
        'Mozilla/4.0 (Windows; MSIE 6.0; Windows NT 5.2)',
        'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)',
        'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)',
        'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)',
        'Mozilla/5.0 (compatible; WOW64; MSIE 10.0; Windows NT 6.2)',
        'Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; en) Presto/2.9.168 Version/11.52',
        'Opera/9.80 (Windows NT 6.1; WOW64; U; en) Presto/2.10.229 Version/11.62',
        'Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML, like Gecko) Version/7.2.1.0 Safari/536.2+',
        'Mozilla/5.0 (MeeGo; NokiaN9) AppleWebKit/534.13 (KHTML, like Gecko) NokiaBrowser/8.5.0 Mobile Safari/534.13',
        'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; en-US) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27',
        'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27',
        'Mozilla/5.0 (iPad; CPU OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3',
        'Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3',
        'Mozilla/5.0 (iPad; CPU OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3',
    ]

Scrapy

Scrapy可以说是Python爬虫界最著名的一个框架了,在我看来也是最全的一个框架,当然优缺点也是很明显的:

  • 文档太多,有很多几分钟就能上手的例子
  • Scrapy默认将已经抓取过的和队列中的请求都存储在内存中,不过可以使用JOBDIR将进度持久化
  • Scrapy本身不支持分布式,要支持分布式,需要依靠scrapy-redis,但是该库很久没人维护了,不过逻辑比较简单,可以自己造轮子
  • Scrapy要抓取Js生成的页面,需要使用其他的工具来辅助,比如Splash,Selenium等,现在(2023)我更推荐scrapy-playwright
  • Scrapy可以通过设置CONCURRENT_REQUESTS设置并发的线程数量,默认是16,另外一个控制是CONCURRENT_REQUESTS_PER_DOMAIN默认是8。两个变量都是有作用的,并发有多大,程序就会开多少个子线程。当然,具体怎么执行还是得看CPU,例如,在4核8线程上面,同时仅有8个线程在运行(对于Python来说,其实仅有一个线程),超过的线程,基本上属于等待唤醒的状态,等那8个线程执行完毕或者遇到IO阻塞的时候才会被唤醒。这一点,对于网络延迟很大的任务非常有用,不用再所有线程去等待了。

在学习scrapy的过程中,如果有看源码的兴趣,建议顺便看看scrapy-redis,虽然该项目很少维护,但是却非常有利于搞懂scrapy框架。

限制

  • Cloudflare我现在没找到什么更好的方法,但是有一个ZenRows提供了付费服务,小贵,但是确实有用,并且API很简单

基本框架

新建项目,scrapy startproject test目录结构如下(mac里面没有把scrapy命令放到bin里面去,直接搜索命令所在地吧):

1
2
3
4
5
6
7
8
9
10
11
.  
├── scrapy.cfg
└── ProjectName
  ├── __init__.py
  ├── items.py   # 定义items
  ├── pipelines.py # 定义items的处理器
  ├── settings.py   # 全局社会自
  └── spiders     # 爬虫文件
      ├── __init__.py
    ├── 一个爬虫
    └── 又一个爬虫

常用命令

1
2
3
4
5
6
7
8
9
scrapy startproject test    # 创建项目
scrapy genspider haofly haofly.net # 新建爬虫
scrapy list # 列出当前project所有的spider
scrapy bench # 基准测试,测试当前硬件的情况下最大的抓取速度
scrapy check # scrapy自身的单元测试,很多人都不建议用,很难用,而且没没什么作用

scrapy crawl haofly --loglevel=critical # 开始一个爬虫,设置日志级别
scrapy crawl haofly --output=output.json # 开始某个爬虫,output参数会把抓取到的item保留到文件中去
scrapy crawl zhaopin -s JOBDIR=crawls/zhaopni-1 # -s参数可以将进度保存下来,这样可以保留爬取进度,下次只需要再执行该命令就能从中断的地方继续

通用设置

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
BOT_NAME   # 定义项目名称
ITEM_PIPELINES = {
'project.pipelines.PostPipeline': 300, # 处理获取到的Items的各种方法,会按照后面数值从小到大的顺序依次处理
}
EXTENSIONS = {
'scrapy.extensions.throttle.AutoThrottle': 0, # 定义扩展程序,AutoThrottle表示自动限制频率的扩展,还要在下面继续配置其参数
}
SPIDER_MIDDLEWARES = { # 中间件
'scrapy.spidermiddlewares.httperror.HttpErrorMiddleware': 50, # 处理非200状态码的中间件
# 'youtube.middlewares.MyCustomSpiderMiddleware': 543,
}

# 配置AutoThrottle插件,会根据爬取的网站的负载自动限制爬取速度,不开的话默认的下载延迟就是0。通过自动调节,可以自动调节并发数和下载延迟。
AUTOTHROTTLE_ENABLED=True  # 是否开启自动限制频率,不开简直太恐怖了
AUTOTHROTTLE_START_DELAY=1 # 以秒为单位,默认为5
AUTOTHROTTLE_MAX_DELAY=10 # 默认为60
AUTOTHROTTLE_DEBUG=False

CONCURRENT_REQUESTS=16 # 全局并发线程的数量
CONCURRENT_REQUESTS_PER_DOMAIN=8 # 针对某一个域名的最大并发量
CONCURRENT_ITEMS=100 # 同时处理的Items的最大值
REACTOR_THREADPOOL_MAXSIZE=20 # 线程池,主要是为了减少创建销毁线程的开销

COOKIES_ENABLED=False # 是否开起cookie
LOG_LEVEL='DEBUG' # LOG级别,在下面介绍了log的几种级别,默认级别是INFO

REDIRECT_ENABLED=False # 禁止重定向
RETRY_ENABLED=False # 关闭重试

DUPEFILTER_DEBUG = True # 打开dupefilter的debug

爬虫主体

当一个请求被yield以后,会立马添加到队列中去;当线程空闲的时候则会从队列中取出;然后经过middleware中间件对request进行处理,最后发起真正的请求。

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 scrapy
from scrapy import Request

class TestSpider(scrapy.Spider):
name = 'test'
allowed_domains = ['test.com']
start_urls = ['https://www.test.com/']
handle_httpstatus_list = [301] # 捕获非200的响应,301表示允许重定向。HTTPERROR_ALLOW_ALL = True表示捕获所有

def start_requests(self):
"""生成初始请求"""
self.crawler.stats.set_value('key', 'value') # 可以自定义给爬虫设置全局的状态字段
self.crawler.stats.inc_value('my_custom_count') # 还能设置自增字段
self.crawler.stats.max_value('my_custom_count') # 还能设置最大值或最小值min_value(多线程依然能实现)
yield Request(url, self.parse, meta={})

def parse(self, respone):
yield item

def parse_another(self, response):
# 需要注意的是,所有的回调函数,要么返回item list,要么返回request list,如果什么都不返回,例如,直接写了个self.another_func(...),如果后面没有yield方法,那么该函数并不会执行
self.parse_page(response) # 即使你在parse_page里面返回了yield,该函数也不会执行,最好这样
for item in self.parse_page(response):
yield item

def closed(self, reason): # 爬虫结束的hook,正常结束reason都是"finished"
print(self.crawler.stats.get_stats()) # 获取爬虫的状态信息,就是最后的总结

请求与响应

1
2
3
4
5
6
7
8
request.meta['proxy'] = 'http://xxx.xxx.xxx.xxx:2333'	# 给meta设置proxy字段则会添加代理

response.body # 获取响应的body
response.body_as_unicode() # 获取响应编码后的内容
response.requests # 获取相应的请求
response.status # 获取HTTP状态码
response.url # 获取请求的URL
response.headers # 获取请求头

网页解析

1
respons.css('li')	# css选择器 

Items

Item只是对爬取结果对象的一个简单封装,提供了简洁的语法。需要注意的是,在pipeline里面处理item的时候必须return item否则,其他的item会接受不到,而且会打印一个莫名其妙的None,终端也不会显示抓取到的item,也就不将讲item输出到文件了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# items.py中对item进行定义
import scrapy
class ResultItem(scrapy.Item):
name = scrapy.Field()
url = scrapy.Field()

# 在爬虫里面可以这样使用
def parse(self, response):
item = scrapy.Items()
item['id'] = response.xpath('//td[@id="item_id"]/text()').re(r'ID: (\d+)')
item['name'] = response.xpath('//td[@id="item_name"]/text()').extract()
item['description'] = response.xpath('//td[@id="item_description"]/text()').extract()
yield item

# item的常用方法
post['name']
post.get('name')
post.get('name', 'no value')
post.keys()
post.items()
dict(post) # 将Item转换为字典
Post(dist) # 从字典创建Item

Pipeline

处理item的管道,用来处理爬虫抽取到的数据,可以在这里面进行数据的验证和持久化等操作。要使用pipeline,必须在设置里面将ITEM_PIPELINES的注释取消掉

1
2
3
4
5
6
7
8
class TestPipeline(object):
def open_spider(self, spider): # 爬虫开始时候执行
print('open')
self.session = DB_Session()

def close_spider(self, spider): # 爬虫结束时候执行
print('close')
self.session.close()

Middleware中间件

可以直接在middlewares.py中进行定义

DownloadMiddleware下载中间件

所有的请求在yield Request的时候被加入队列,当调度器空闲的时候则取出来,然后以此被下载中间件进行处理(即process_request),最后发起真正的请求。需要注意的是,process_request是每个中间件顺序执行的,但是process_response则是每个中间件倒序执行的。

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
class MyMiddleware(object):
process_request(self, request, spider):
"""
重试的时候并不会进入这里面
:return None: 返回None或者无返回,则会继续处理该请求,继续执行被接下来的中间件处理
: Response: 返回Response,则会继续处理该请求,但是不会被其他中间件处理,相当于不请求了直接给一个响应
Request: 返回Request,则不会继续处理该请求,相当于新建了一个请求,重新来
:raise IgnoreRequest: 将会调用process_exception
"""
pass

process_response(self, request, response, spider):
"""
所有的有http状态码的响应都会到这里来
:return Response: 将会继续往下执行
Request: 该链条会终端,重新来一个请求
:raise IgnoreRequest:
"""
pass

process_exception(self, exception, spider):
"""
所有的没有http状态码的异常
:return None: 将会继续处理
Response: 返回一个正常的Response
Request: 重新来一个请求
"""
pass

Signal信号

scrapy内部类似于事件触发的机制,通知某件事情发生了。

1
2
3
4
5
6
7
8
9
10
11
engine_started()	# 当Scrapy引擎启动爬取时发送该信号
engine_stopped() # 当Scrapy引擎停止时发送该信号
item_scraped() # 当item被爬取,并通过所有Pipeline后(没有被丢弃(dropped),发送该信号
item_dropped()
spider_closed()
spider_opened()
spider_idle(spider) # spider处于空闲时候的信号
spider_error
request_scheduled(request, spider) # 当引擎调度一个Request对象用于下载时,该信号被发送
response_received(resopnse, request, spider)
response_downloaded(response, request, spider)

日志用法

日志有几种级别,默认是DEBUG,会打印所有的信息,可以在配置文件中进行配置,也可在抓取命令上指定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 日志打印,日志的几种级别,默认是DEBUG,抓取的时候可指定log级别,也可在配置文件中配置
CRITICAL: 严重错误(critical)
ERROR - 一般错误(regular errors)
WARNING - 警告信息(warning messages)
INFO - 一般信息(informational messages)
DEBUG - 调试信息(debugging messages)

# 如果在spider里面使用,可以直接
self.logger.critical('发生严重错误')

# 如果在其他地方,可以这样用
import logging
logger = logging.getLogger()
logger.warning("This is a warning")

###暂停与继续

scrapy提供了简单的方法以便于程序意外终止或者主动停止,仅需要在运行爬虫的时候添加参数,用于指定队列状态存储目录,scrapy会将序列化后的队列状态存储在该目录中

1
2
3
4
scrapy crawl somespider -s JOBDIR=crawls/somespider-1

# 如果想让某个url允许出现重复,那么可以给Request这个参数dont_filter。如果返回200,但是还是想重新请求,想让该url不会被filter掉,那么可以直接生成一个相同的request将dont_filter设置为True就行了
yield Request(url=url, dont_filter=True)

Telnet

scrapy运行的时候会打开一个6023端口,用于实时查看爬虫当前的进度。直接telnet 127.0.0.1 6023即即可进入

1
2
3
4
5
6
7
8
9
10
# est()命令
time()-engine.start_time: 总的执行时间
len(engine.downloader.active): 正在下载的请求数量
len(engine.slot.inprogress): 当前处理进程数量
len(engine.slot.scheduler.mqs): 当前还在排队的请求数量,被yield以后就被放到这里面
len(engine.scraper.slot.queue):
len(engine.scraper.slot.active): 当前等待处理的响应的书俩昂
engine.scraper.slot.active_size: 所有的响应总的大小
engine.scraper.slot.itemproc_size: 有多少个item等待被处理
engine.scraper.slot.needs_backout()

其他语法

1
2
3
4
5
6
7
8
9
10
11
12
# 元素选择可使用xpath和css方式,但一般都用css方式方便直观点,比如
response.css('div.no-txt-box p.tit) # 获取符合条件的元素的列表 response.css('p.class::text)[0].extract() # 获取p元素的内容

# 将item传递到下一级请求中去
yield Request(url, callback=..., meta={'item': item})
item = response.meta['item']

# 处理最开始的请求,生成初始url列表
def start_requests(self):
"""产生种子url"""
for url in start_urls:
yield Request(url, self.parse)

##TroubleShooting

  • 安装出错libffi

    1
    2
    3
    No package 'libffi' found
    c/_cffi_backend.c:13:17: fatal error: ffi.h: No such file or directory
    #include <ffi.h>

    需要安装这个sudo apt-get install libffi-dev

  • ImportError: No module named twisted.internet或者No module named twisted
    执行pip3 install twisted,如果出现错误No matching distribution found for Twisted那么就是系统存在多个python版本导致找不到解压twisted包的库,这时候需要先安装sudo apt-get install bzip2 libbz2-dev,然后重新安装python3即可

  • 无法捕获除200以外的错误
    首先,像上面的配置文件中添加HttpErrorMiddleware中间件,然后在spider里面定义需要捕获哪些错误

    1
    2
    class MySpider(CrawlSpider):
    handle_httpstatus_list = [404]
  • 安装出错no module named w3lib.http
    pip install w3lib

# 打印日志方法
from scrapy import log
log.msg("lalalala", level=log.INFO)

暂停与继续

# 要使用暂停与继续功能,必须编写通用爬虫才能,也就是说Spider继承自CrawlSpider而不是BaseSpider,BaseSpider仅仅会抓取start_urls里面的
  • 处理不同的item
# pipeline处理不同的items
if isinstance(item, FeedItem)  # 这种判断方式感觉好鸡肋

# 在pipeline中丢弃items不再处理


raise DropItem("Duplicate item found.")
1
2
3
4
# 指定需要捕获的html状态
handle_httpstatus_list = [404, 502]

# no module

/tmp/xmlXPathInitipwvpamp.c:1:26: 错误:libxml/xpath.h:没有那个文件或目录

User-Agent列表

上次爬一个代理网站发现返回521错误,排查了好久居然发现是User-Agent错误,可我明明之前也是选择了不同的User-Agent的呀,难道服务器会记录一个IP对应一个User-Agent.这里罗列一下常用的User-Agent。更全的列表可以参考user-agent-list

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
'Mozilla/5.0 (Android; Tablet; rv:14.0) Gecko/14.0 Firefox/14.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:21.0) Gecko/20100101 Firefox/21.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:21.0) Gecko/20130331 Firefox/21.0',
'Mozilla/5.0 (Windows NT 6.2; WOW64; rv:21.0) Gecko/20100101 Firefox/21.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.11 (KHTML, like Gecko) Ubuntu/11.10 Chromium/27.0.1453.93 Chrome/27.0.1453.93 Safari/537.36',
'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.94 Safari/537.36',
'Mozilla/5.0 (iPhone; CPU iPhone OS 6_1_4 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) CriOS/27.0.1453.10 Mobile/10B350 Safari/8536.25',
'Mozilla/4.0 (Windows; MSIE 6.0; Windows NT 5.2)',
'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)',
'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)',
'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)',
'Mozilla/5.0 (compatible; WOW64; MSIE 10.0; Windows NT 6.2)',
'Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; en) Presto/2.9.168 Version/11.52',
'Opera/9.80 (Windows NT 6.1; WOW64; U; en) Presto/2.10.229 Version/11.62',
'Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML, like Gecko) Version/7.2.1.0 Safari/536.2+',
'Mozilla/5.0 (MeeGo; NokiaN9) AppleWebKit/534.13 (KHTML, like Gecko) NokiaBrowser/8.5.0 Mobile Safari/534.13',
'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; en-US) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27',
'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27',
'Mozilla/5.0 (iPad; CPU OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3',
'Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3',
'Mozilla/5.0

安装方法

CentOS使用包的方式安装最新MariaDB,CentOS安装client直接yum install mysql而不是client,而安装mysql则直接用yum install -y mysql mysql-server mysql-dev mysql-devel,CentOS7上已经用mariadb代替了mysql,这样子使用:

1
2
3
4
5
6
7
8
9
10
yum install mariadb-server mariadb-client mariadb-devel -y
systemctl start mariadb.service # 启动服务
systemctl enable mariadb.service # 开机启动

# 彻底删除mysql
sudo systemctl stop mysql
sudo apt-get purge mysql-server mysql-client mysql-common mysql-server-core-* mysql-client-core-*
sudo rm -rf /var/lib/mysql
sudo rm -rf /etc/mysql
sudo deluser mysql && sudo delgroup mysql

另外,更新方式可以参考这篇文章: 如何更新到MariaDB 10.4

Ubuntu:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 安装最新版本mariadb,需要先导入对应的镜像库https://downloads.mariadb.org/mariadb/repositories
sudo apt-get install mariadb-server mariadb-client libmariadbd-dev

## 安装mysql,可以使用https://dev.mysql.com/downloads/repo/apt/的方式
wget https://dev.mysql.com/downloads/repo/apt/mysql-apt-config_0.8.15-1_all.deb
sudo dpkg -i mysql-apt-config_0.8.15-1_all.deb # 会进入版本选择界面,选择正确的版本,然后ok
sudo apt-get update && sudo apt-get install mysql-server即可

# 如果是开发,还需要安装
sudo apt-get install libmariadb-client-lgpl-dev
sudo ln -s /usr/bin/mariadb_config /usr/bin/mysql_config

# 第一次登录使用
sudo mysql # mysql8可以直接这样进入然后设置密码
sudo mysql -u root
阅读全文 »