This is a [ Personal Note ]
单纯记录下这次写定向爬虫中遇到的问题。其实很多东西只是因为忘了,不是因为没学过,这次记录下来以便日后回顾。
rsync
rsync
相比scp
有很多优势,可以完全替代。
Demo
1 | $ rsync [email protected]:~/data.db ~/Desktop/ |
1 | $ rsync -P [email protected]:~/big_db.db ~/Desktop/ |
-P
的作用等同于--partial
和--progress
参数,前者可以让传输实现断点续传,后者可以显示进度条。
Manual
语法
1 | rsync [OPTION]... SRC DEST |
对应于以上六种命令格式,rsync有六种不同的工作模式:
- 拷贝本地文件。当SRC和DES路径信息都不包含有单个冒号":"分隔符时就启动这种工作模式。如:
rsync -a /data /backup
- 使用一个远程shell程序(如rsh、ssh)来实现将本地机器的内容拷贝到远程机器。当DST路径地址包含单个冒号":"分隔符时启动该模式。如:
rsync -avz *.c foo:src
- 使用一个远程shell程序(如rsh、ssh)来实现将远程机器的内容拷贝到本地机器。当SRC地址路径包含单个冒号":"分隔符时启动该模式。如:
rsync -avz foo:src/bar /data
- 从远程rsync服务器中拷贝文件到本地机。当SRC路径信息包含"::"分隔符时启动该模式。如:
rsync -av [email protected]::www /databack
- 从本地机器拷贝文件到远程rsync服务器中。当DST路径信息包含"::"分隔符时启动该模式。如:
rsync -av /databack [email protected]::www
- 列远程机的文件列表。这类似于rsync传输,不过只要在命令中省略掉本地机信息即可。如:
rsync -v rsync://192.168.78.192/www
选项
1 | -v, --verbose 详细模式输出。 |
screen
nohup
不香,screen
更方便。
Demo
1 | $ screen -S py_script |
Manual
语法
1 | screen [-AmRvx -ls -wipe][-d <作业名称>][-h <行数>][-r <作业名称>][-s ][-S <作业名称>] |
选项
1 | -A 将所有的视窗都调整为目前终端机的大小。 |
常用screen参数
1 | screen -S yourname -> 新建一个叫yourname的session |
Python3 utf-8
1 | # -*- coding: utf-8 -*- |
1 | PYTHONIOENCODING=utf-8 python3 main.py |
1 | open('./file.txt', 'w', 'utf-8') |
1 | logging.FileHandler(encoding='utf-8') |
以及所有有io的地方encoding='utf-8'
argparse
arhparse - Parser for command-line options, arguments and sub-commands
argparse包是处理命令行参数最好的工具,你的每个参数都可以调整众多选项
Demo:
1 | import argparse |
logging
不管是输出重定向还是tee命令,都不如专门的日志模块好用。
Demo
1 | import logging |
logging同时输出到屏幕和文件
1 | logger = logging.getLogger() # 不加名称设置root logger |
sqlite
python3操作sqlite3是非常方便的,定向爬虫用sqlite3储存数据基本是够用了。
Demo
1 | with sqlite3.connect(db_path) as conn: |
1 | with sqlite3.connect(db_path) as conn: |
APIs
No | API & Description |
---|---|
1 | sqlite3.connect(database [,timeout ,other optional arguments]) 该 API 打开一个到 SQLite 数据库文件 database 的链接。您可以使用 “:memory:” 来在 RAM 中打开一个到 database 的数据库连接,而不是在磁盘上打开。如果数据库成功打开,则返回一个连接对象。当一个数据库被多个连接访问,且其中一个修改了数据库,此时 SQLite 数据库被锁定,直到事务提交。timeout 参数表示连接等待锁定的持续时间,直到发生异常断开连接。timeout 参数默认是 5.0(5 秒)。如果给定的数据库名称 filename 不存在,则该调用将创建一个数据库。如果您不想在当前目录中创建数据库,那么您可以指定带有路径的文件名,这样您就能在任意地方创建数据库。 |
2 | connection.cursor([cursorClass]) 该例程创建一个 cursor,将在 Python 数据库编程中用到。该方法接受一个单一的可选的参数 cursorClass。如果提供了该参数,则它必须是一个扩展自 sqlite3.Cursor 的自定义的 cursor 类。 |
3 | cursor.execute(sql [, optional parameters]) 该例程执行一个 SQL 语句。该 SQL 语句可以被参数化(即使用占位符代替 SQL 文本)。sqlite3 模块支持两种类型的占位符:问号和命名占位符(命名样式)。例如:cursor.execute(“insert into people values (?, ?)”, (who, age)) |
4 | connection.execute(sql [, optional parameters]) 该例程是上面执行的由光标(cursor)对象提供的方法的快捷方式,它通过调用光标(cursor)方法创建了一个中间的光标对象,然后通过给定的参数调用光标的 execute 方法。 |
5 | cursor.executemany(sql, seq_of_parameters) 该例程对 seq_of_parameters 中的所有参数或映射执行一个 SQL 命令。 |
6 | connection.executemany(sql[, parameters]) 该例程是一个由调用光标(cursor)方法创建的中间的光标对象的快捷方式,然后通过给定的参数调用光标的 executemany 方法。 |
7 | cursor.executescript(sql_script) 该例程一旦接收到脚本,会执行多个 SQL 语句。它首先执行 COMMIT 语句,然后执行作为参数传入的 SQL 脚本。所有的 SQL 语句应该用分号(;)分隔。 |
8 | connection.executescript(sql_script) 该例程是一个由调用光标(cursor)方法创建的中间的光标对象的快捷方式,然后通过给定的参数调用光标的 executescript 方法。 |
9 | connection.total_changes() 该例程返回自数据库连接打开以来被修改、插入或删除的数据库总行数。 |
10 | connection.commit() 该方法提交当前的事务。如果您未调用该方法,那么自您上一次调用 commit() 以来所做的任何动作对其他数据库连接来说是不可见的。 |
11 | connection.rollback() 该方法回滚自上一次调用 commit() 以来对数据库所做的更改。 |
12 | connection.close() 该方法关闭数据库连接。请注意,这不会自动调用 commit()。如果您之前未调用 commit() 方法,就直接关闭数据库连接,您所做的所有更改将全部丢失! |
13 | cursor.fetchone() 该方法获取查询结果集中的下一行,返回一个单一的序列,当没有更多可用的数据时,则返回 None。 |
14 | cursor.fetchmany([size=cursor.arraysize]) 该方法获取查询结果集中的下一行组,返回一个列表。当没有更多的可用的行时,则返回一个空的列表。该方法尝试获取由 size 参数指定的尽可能多的行。 |
15 | cursor.fetchall() 该例程获取查询结果集中所有(剩余)的行,返回一个列表。当没有可用的行时,则返回一个空的列表。 |
selenium & webdriver
“Failed to decode response from marionette” message in Python/Firefox headless scraping script
Running Selenium WebDriver tests using Firefox headless mode on Ubuntu
chrome需要使用chromewebdriver
firefox需要使用geckodriver
google一下第一个就是。在macOS上brew
可以直接装geckodriver,然而在服务器上还是要wget
。
如果没有添加PATH
,在初始化webdriver的时候加一下executable_path
:
1 | webdriver.Firefox(executable_path='./path/to/geckodriver') |
firefox headless
在服务器上跑肯定是没屏幕没GUI的,所以要多添加headless选项或者选择配置下虚拟屏幕,二选一:
- 在代码中,给webdriver添加options。
1 | firefox_options = webdriver.FirefoxOptions() |
- 在执行脚本前使用Xvfb,以apt包管理为例:
1 | sudo apt-get update |
Message: Failed to decode response from marionette
如果在爬虫运行的开始就遇到这个异常,那么最大的可能是已经有firefox进程在占用端口,所以可以使用pkill firefox
解决。
我的情况是,爬虫一开始运行地很好,第二天上午再去服务器上查看的时候发现崩溃了。完整异常信息是selenium.common.exceptions.WebDriverException: Message: Failed to decode response from marionette
并且,在geckodriver.log的最后中有如下记录:
1 | A content process crashed and MOZ_CRASHREPORTER_SHUTDOWN is set, shutting down |
可以看到是firefox崩了,原因可能有很多。但是我猜最大的原因可能是内存泄漏,或者2GB RAM的服务器根本不够用。所以做出以下尝试,目前的效果还可以,没有再次遇到崩溃的情况。
禁用firefox缓存
如果是爬虫一直在用一个webdriver实例,也就说一直有个firefox进程在一直运行,内存占用确实可能会越来越大。初始化webdriver的时候禁用缓存可解决:
1 | profile = webdriver.FirefoxProfile() |
**Update:**感觉效果并不好,不知道是参数没有产生作用还是firefox有问题。只需要一个下午,firefox的内存占用就可以从15%到60%,问题可能就是在firefox这了,firefox内存泄漏??换用chrome试试,结果还是遇到了问题。selenium调用chrome访问目标网站返回空白页面,访问其它https页面则正常。然后添加了以下参数:
1 | chrome_options = webdriver.ChromeOptions() |
然而,在访问其它http站点时正常,在返回目标网页时依旧是空白页面(目标网站是jsp页面,不知道和这个有没有关系)。
实在无奈,完全不知道怎么解决,用回firefox。蠢办法,爬1000页之后重启webdriver和浏览器继续爬。
另外chrome的有些参数也是很有用的:
1 | chrome_options.add_argument("--headless") |
添加delay
1 | time.sleep(2) # ;) |
因为stackoverflow上有人说可能是遇到了竞争条件,我也不太清楚为什么geckodriver和firefox之间的通信会出现竞争。由于目标网站的简单性,还有网站本身响应就很慢,我一开始写的时候确实1s的delay都没加,考虑到减轻我和目标服务器的负担,加上总归是好点。
而且如果你的爬虫启动了多个浏览器,delay可以让它们稍微歇一歇,免得跑一半又出问题。
后来,这个帖子update了一下,加了delay他的爬虫还是崩了,哈哈哈果然不对,然后他选择加内存。
增大server RAM
加钱,冲冲冲。 钱也解决不了内存泄漏