搞一个aws账号,创建了一个ec2,用的ubuntu镜像,不过发现ec2不能直接用账号密码登录,需要用证书登录,好吧,那就用证书登录吧

putty

putty->Connection->Auth->Private key file for authentication 里选择在aws下载的私钥证书 xxx.pem, 然后登陆,结果提示如下错误:

Unable to use key file "xxx.pem" (OpenSSH SSH-2 private key)

What? 不能使用OpenSSH SSH-2 private key,密钥有问题?什么情况?好吧,没用过证书登录,以前都是直接用账号密码登录的,这有点尴尬,搜了一下,貌似是OpenSSH私钥与putty使用的私钥格式有点不一样,需要转换一下,好吧,去 https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html 这里下载一个puttygen.exe

puttygen

puttygen->Conversions->Import Key->Save private key 里把在aws下载的私钥证书 xxx.pem 转换为 xxx.ppk,再用putty尝试登陆,结果提示如下:

Please login as the user "ubuntu" rather than the user "root".

好吧,这镜像还是默认禁用root的,那就用ubuntu登陆吧,OK,登陆成功,这,虽说用证书是免密登陆,不过这一顿操作,反正也是测试服务器,还是改成账号密码登录吧

编辑 /etc/ssh/sshd_config
sudo vim /etc/ssh/sshd_config

修改 PermitRootLogin prohibit-passwordPermitRootLogin yes
这是设置允许root登陆

修改 PasswordAuthentication noPasswordAuthentication yes
这是设置允许使用账号密码登录

保存退出并重启ssh服务
sudo service ssh restart

哦对,还差一步,要重置root的密码

sudo passwd root

OK,现在就可以直接使用账号密码登录,也可以使用root账号直接登陆了。

最近搞了个简单的文章转视频上传到youtube,需要采集点文章来生成视频,所以打算用Scrapy采集点今日头条的文章,目标是抓取其中一个分类的几十篇文章内容和下载其中的图片导出json数据文件用来生成视频,简单做一下记录。

打开头条网站,有多个分类,打开娱乐版块,查看源代码发现是没有文章内容,看来是js加载的,F12查看分析找到文章数据的接口链接
https://www.toutiao.com/api/pc/feed/?category=news_entertainment&utm_source=toutiao&widen=1&max_behot_time=0&max_behot_time_tmp=0&tadrequire=true&as=A1B5FBF1A7CA537&cp=5B172A5593E7AE1&_signature=oNvqZwAA-9u3ZjcQBbvTJ6Db6n
在浏览器里直接打开这个链接,很好,返回json结构的数据,刷新一下,返回不同的文章,嗯,这么easy,一个链接就搞定了。

那还等什么,新建Scrapy项目抓一下试试,哈哈,什么也没有,看一下Scrapy的输出 DEBUG: Forbidden by robots.txt 原来是头条禁止爬虫抓取了,需要设置Scrapy的参数 ROBOTSTXT_OBEY = False,再试一下还是不行,直接返回

{
  "message": "error",
  "data": [],
  "has_more": false
}

没有文章数据,构造header加上User-Agent跟Referer

headers = {
        "Referer": "https://www.toutiao.com/ch/news_entertainment/",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0"
    }

OK,正常返回数据了,多次请求,嘿,返回的文章基本都一样,看来还是不可以哦,再加上cookie试试,用固定的 tt_webid=6563786091257824776;,多次请求,发现跟浏览器里刷新是一样的了,会返回不同的文章,倒是基本可以满足一次抓取几十篇文章需求了。

不过既然搞了那再深入研究一下看看这几个参数是干啥的嘛

category=news_entertainment 分类,对应头条的分类,news_entertainment是娱乐

utm_source=toutiao 固定

widen=1 固定

max_behot_time=0max_behot_time_tmp=0值相同,是个时间戳,初次请求是0,每次请求返回的数据中都会带有下一次请求的时间戳 "next": {"max_behot_time": 1528358256}

tadrequire=true带广告,false不带广告

as=A1B5FBF1A7CA537cp=5B172A5593E7AE1是简单的时间加密,网上搜一下,很容易找到python的翻译代码,不加这两个参数,也能返回文章数据

def get_as_cp():
    t = int(math.floor(time.time()))
    e = hex(t).upper()[2:]
    m = hashlib.md5()
    m.update(str(t).encode(encoding='utf-8'))
    i = m.hexdigest().upper()
    if len(e) != 8:
        as_key = "479BB4B7254C150"
        cp_key = "7E0AC8874BB0985"
        return as_key, cp_key
    n = i[:5]
    a = i[-5:]
    r = ""
    s = ""
    for k in range(5):
        s += n[k]+e[k]
        r += e[k+3]+a[k]

    as_key = "A1" + s + e[-3:]
    cp_key = e[0:3] + r + "E1"

    return as_key, cp_key

_signature=oNvqZwAA-9u3ZjcQBbvTJ6Db6n 是个签名,测试发现这个参数值的生成跟浏览器UA有关,换个UA就没有数据返回了,_signature跟UA匹配才会返回数据,网上倒是没有找到python的翻译代码,看来这个有点难搞,先用固定值,构造链接

url = 'https://www.toutiao.com/api/pc/feed/?category=news_entertainment&utm_source=toutiao&widen=1' \
          '&max_behot_time={0}' \
          '&max_behot_time_tmp={0}' \
          '&tadrequire=true' \
          '&as={1}' \
          '&cp={2}' \
          '&_signature=oNvqZwAA-9u3ZjcQBbvTJ6Db6n'

发现第一次请求max_behot_time=0时正常返回文章数据,之后带上新的时间戳后,就没有数据了,看来这个签名还跟时间戳有点关系,找一下js代码看看,

function e(t) {
        var e = ascp.getHoney(),
            i = "";
        window.TAC && (i = TAC.sign("refresh" === t ? 1 : r.params.max_behot_time_tmp)), r.params = _.extend({}, r.params, {
            as: e.as,
            cp: e.cp,
            max_behot_time: "refresh" === t ? 0 : r.params.max_behot_time_tmp,
            _signature: i
        })
    }

果然,签名的生成跟max_behot_time_tmp的值有关,貌似是用TAC.sign()函数生成,找一下这个函数看看,emmm~,乱码~,尴尬了,在F12控制台敲入TAC.sign,发现还是乱码是什么鬼,这个函数还搞了一下加密?算了,算了,大道至简,还是搜搜看有没有人搞过吧,不过没有找到python的翻译代码,让我自己写,哈哈,那还是写不出来的,不费这个劲了,直接用解析js的方式来获取这个值吧,搜了一下,可以用pyExecJS这个模块来解析js,但是直接把头条的js文件加载进来发现会报错,不过倒是找到一份整理过的js代码 toutiao-TAC.sign.txt,https://bbs.125.la/thread-14115046-1-1.html,感谢nliger2015的分享,可以直接拿来用没有报错。

def get_as_cp_signature(max_behot_time=0):
    with open('toutiao-TAC.sign.txt', encoding='utf-8') as f:
        js = f.read()
    ctx = execjs.compile(js)
    s = ctx.call('get_as_cp_signature', max_behot_time)
    return json.loads(s)

哈哈,很好,可以直接返回as,cp,_signature三个参数值,不错不错,之前as和cp的翻译函数不需要了,获取文章列表数据基本差不多了,接下来开始搞文章详情。

先是直接用xpath获取内容,哈哈,结果取回来的全是None,嗯?xpath写的不对?放到浏览器测试没问题的,查看源代码看一下,我去,详情页的内容也是用js加载的,想要的内容都在js里呢,都放到BASE_DATA这个变量里了,是json结构的对象,不过不能直接用json模块解析,因为不全是用的双引号,json模块解析会报错,唉,好吧,既然在js里,那直接用js解析一下,要是能直接返回BASE_DATA这个变量对象,不就是拿到了文章的结构数据,用起来不就更省事了,不就是想怎么用就怎么用了,哈哈。

data = response.xpath('//script[contains(text(),"var BASE_DATA")]/text()').extract_first()
    if data:
        data = data + "\nvar a = function(){return BASE_DATA;}"
        ctx = execjs.compile(data)
        data = ctx.call('a')
        print(data['articleInfo']['title'])

先用xpath取出需要的js代码部分,再加个函数return这个变量,然后用pyExecJS解释执行一下,返回文章详情数据,执行下来,倒是没问题,也正常返回数据了,而且确实是直接可以拿来用的,不过输出一下,中文全变成了“锟斤拷锟斤拷锟叫★拷锟斤拷锟斤拷钱锟斤...”,这是什么鬼?编码有问题?尝试转码后再传递给js执行,结果报错,emmm,什么鬼,各种编码解码尝试无果,放弃js解析,改用正则,哈哈,还是正则好用,指哪拿哪

data = response.xpath('//script[contains(text(),"var BASE_DATA")]/text()').extract_first()
data = re.search(r"title:\s*'(.*?)',\s*content:\s*'(.*?)',[\s\S]*?tags:\s*(\[.*\]),", data)
if data:
    title, content, tags = data.group(1, 2, 3)

直接拿到title,content,tags的数据,再对拿到的数据进行一下处理,加个下载图片的pipeline把图片也下载下来,嗯,基本搞定,采集导出的json数据文件可以直接用来生成视频。

还有点小问题,重复文章数据,因为写这个小爬虫,就是采集文章用来生成视频的,不过生成视频比较慢,嗯是挺慢的,所以一次采集个几十篇文章即可,在一次抓取中,返回的文章数据是没有重复的,不过下一次再重新启动爬虫抓取时,就会跟上次采集的文章有重复的,时间间隔不长的话,重复的还不少,也加上cookie固定ttweb_id的值试了下发现效果不大,一样会有重复的数据,所以就把每次抓取的文章链接存到文件里,每次开始抓取前,先读入存放链接的文件把已经抓取的链接放到列表里,每次发送文章详情页request前先判断一下是不是抓取过,在列表中的链接直接跳过,等到抓取完成爬虫关闭时,再把新抓取的链接追加到文件里,等抓取的链接比较多的时候,这种方式可能不太好,不过没关系,满足当前的需求就可以了,哈哈。

写了个scrapy小爬虫准备放到闲置的那台windows服务器上跑一下,服务器环境 Windows Server 2012 R2 64位操作系统,先安装Python 3.6.5,直接官网下载64位安装包,安装完成,运行python时,结果弹出 api-ms-win-crt-runtime-l1-1-0.dll 丢失的错误,搜索了一下说是需要安装vc2015运行环境Visual C++ Redistributable Package,下载安装vc_redist.x64.exe 又报 0x80240017 的错误,尴尬了,说这个错误是需要安装 KB2999226 这个补丁程序,好吧,接着下载安装,结果又提示此更新不适用于您的计算机,好嘛,真是尴尬了,接着搜索,说是还需要按顺序安装几个更新,具体是哪些懒得研究了,直接打开系统更新,装了一堆更新重启了一回,然还不行,再手动检查系统更新,又出来3个更新,继续更新,不容易,终于可以了,接下来赶紧进入正题,安装scrapy。

Scrapy的安装

使用pip安装scrapy pip install scrapy

没有意外的报错了

building 'twisted.test.raiser' extension
error: Microsoft Visual C++ 14.0 is required. Get it with "Microsoft Visual C++ Build Tools": http://landinghub.visualstudio.com/visual-cpp-build-tools

这是电脑上没有安装Microsoft Visual C++ 14.0的编译工具,所以无法对源码进行编译,解决的办法一种就是安装Microsoft Visual C++ Build Tools,当然更省事的是直接安装已经预编译好的Twisted包。

可以在 https://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted 这里找到已经预编译好Twisted包的二进制安装包whl文件。

Twisted, an event-driven networking engine.
Twisted‑18.4.0‑cp27‑cp27m‑win32.whl
Twisted‑18.4.0‑cp27‑cp27m‑win_amd64.whl
Twisted‑18.4.0‑cp34‑cp34m‑win32.whl
Twisted‑18.4.0‑cp34‑cp34m‑win_amd64.whl
Twisted‑18.4.0‑cp35‑cp35m‑win32.whl
Twisted‑18.4.0‑cp35‑cp35m‑win_amd64.whl
Twisted‑18.4.0‑cp36‑cp36m‑win32.whl
Twisted‑18.4.0‑cp36‑cp36m‑win_amd64.whl
Twisted‑18.4.0‑cp37‑cp37m‑win32.whl
Twisted‑18.4.0‑cp37‑cp37m‑win_amd64.whl

选择我们需要的Twisted版本,cp后面是Python的版本号,cp36表示Python版本3.6,amd64表示64位。

下载完成之后,进入下载文件所在的文件夹运行以下命令安装Twisted,pip install Twisted-18.4.0-cp36-cp36m-win_amd64.whl,再运行pip install scrapy即可安装成功。

不过如果运行scrapy,一般还会出现如下的错误

    import win32api
ModuleNotFoundError: No module named 'win32api'

需要安装 pywin32,直接pip安装 pip install pywin32,当然如果pip安装失败的话,仍然是可以直接用预编译好的二进制安装包whl文件来安装的。

如果有下载图片的话,可能还会报如下错误

    import Image
ModuleNotFoundError: No module named 'PIL'

需要安装 pillow,直接pip安装 pip install pillow

windows环境下用pip安装一些带有c扩展的包时,可能会踩坑报错,这时直接下载预编译好的二进制安装包。

  1. https://www.lfd.uci.edu/~gohlke/pythonlibs/ 在这里下载预编译好的.whl文件,Ctrl + F 输入python包名查找,找到对应模块包,根据python版本选择下载。
  2. 进入下载.whl文件所在的文件夹,执行命令 pip install 带后缀的完整文件名 即可完成安装。

nginx禁止访问文件或目录的配置方法

nginx禁止访问指定后缀的文件

location ~ .*\.(ini|inc|conf|dat|txt)$ {
    # 正则表达式匹配,禁止访问后缀为ini,inc,conf,dat,txt的文件
    # 修饰符 ~ 表示正则匹配区分大小写,可以使用修饰符 ~* 表示不区分大小写
    deny all;
}

nginx禁止访问指定目录

location ^~ /path1/ {
    # 普通字符串前缀匹配,修饰符 ^~ 表示如果匹配到这条普通规则后,不再进行之后的正则搜索了
    deny all;
}

location ^~ /path2/ {
    deny all;
}

也可以使用正则匹配同时禁止访问多个指定目录

location ~ ^/(App|config|Include|Theme|vendor)/ {
    deny all;
}

nginx禁止访问指定目录中的特定后缀的文件

location ~ /(wp-content|uploads|wp-includes|images)/.*\.php$ { 
    # 禁止访问这些指定目录中的php文件    
    deny all;
}

nginx location指令的uri匹配规则

nginx可以通过配置location指令来对用户请求的uri进行调度,执行不同的文件。

location指令有两种匹配模式,普通字符串前缀匹配和正则表达式匹配。

普通字符串前缀匹配必须以相应的uri开头,如下的匹配规则,必须以/some/path开头的uri才会被匹配,比如/some/path/document.html,而/my-site/some/path不会被匹配。

location /some/path/ {
    ...
}

匹配的顺序是先匹配普通字符串前缀,再匹配正则表达式,不过正则匹配优先级高于普通匹配除非使用修饰符 ^~

语法规则

location [ = | ~ | ~* | ^~ ] uri { ... }

location = /uri        =  开头表示精确匹配,只有完全匹配上才能生效,匹配成功则停止其他匹配。
location ^~ /uri       ^~ 开头对uri路径进行普通字符串前缀匹配,匹配成功则不再进行正则匹配。
location ~ pattern     ~  开头表示区分大小写的正则匹配。
location ~* pattern    ~* 开头表示不区分大小写的正则匹配。
location /uri          不带任何修饰符的普通字符串前缀匹配,优先级低于正则匹配。
location /             通用匹配,任何未匹配到其它location的请求都会匹配到。

匹配执行流程

  1. 查找普通字符串前缀的匹配规则
  2. 以修饰符 = 开头的普通字符串进行精确匹配,如果匹配到,则停止之后的规则查找
  3. 如果普通字符串以修饰符 ^~ 开头,并且是匹配到的最长普通字符串前缀,在匹配到这条规则后不再进行之后的正则查找了
  4. 存储匹配到的最长普通字符串前缀规则
  5. 查找正则表达式的匹配规则
  6. 在所有正则匹配规则中找到第一个匹配项后,就以此项为最终匹配结果
  7. 如果没有查找到正则表达式匹配,则以4中存储的普通匹配为最终匹配结果

普通字符串前缀匹配与配置文件中的前后定义顺序无关,匹配顺序从长到短,正则表达式匹配则受定义的前后顺序影响。