Wait the light to fall

一步一步学习小爬虫

焉知非鱼

饭否

通过一个例子一步一步学习 Python 爬虫。

  • img/@src 提取 img 元素中属性名为 src 的对应的 属性值
  • 通过 Chrome 的审查元素得到的 Xpath 路径和网页源代码中的路径有所不同
  • post 请求中的可变字段一般先通过登录前的一次 get 请求获取

比较疑惑的一个地方是, 使用 Chrome 右键菜单中的 检查, 所找到的 tbody 元素在网页源文件中并不存在。把 tbody 去掉也没有影响。于是决定不使用 tbody 标签了。

//tbody/tr/td[@id="main"]/div[@id="content"]/div[@id="stream"]/ol[@class="wa solo"]/li

完整代码如下:

# -*-coding: utf-8 -*-
import requests
from lxml import etree

class FanFou(object):
    def getDataForm(self):
        base_url = "http://fanfou.com"
        token = etree.HTML(requests.get(base_url).text).xpath('//form/p[@class="act"]/input[@name="token"]/@value')[0]
        dataform = {
            'loginname' : 'qiangyuebing@ali.com',
            'loginpass' : 'sanxingnote7',
            'action' : 'login',
            'token' : token
        }
        return dataform
    
    def getInfo(self, data):
        login_url = "http://fanfou.com/login"
        html = requests.post(login_url, data = data).content
        # messages = etree.HTML(html).xpath('//tbody/tr/td[@id="main"]/div[@id="content"]/div[@id="stream"]/ol[@class="wa solo"]/li')
        messages = etree.HTML(html).xpath('//ol[@class="wa solo"]/li')
        info_List = []

        for li in messages:
            info = {}
            info['home_url']     = li.xpath('a/@href')[0]  # 个人主页
            info['title']        = li.xpath('a[@class="author"]/text()')[0] # 姓名
            info['avatar']       = li.xpath('a[@class="avatar"]/img/@src')[0] # 头像地址
            info['content']      = li.xpath('span[@class="content"]')[0].xpath('string(.)') # 发言
            info['message_time'] = li.xpath('span[@class="stamp"]/a[@class="time"]/text()')[0] # 发言时间
            info['app']          = li.xpath('span[@class="stamp"]/span[@class="method"]')[0].xpath('string(.)') # 使用的客户端
            info_List.append(info)
        return info_List
    
    def savetofile(self, infoList):
        base_url = 'http://fanfou.com'
        fout = open("result.txt", "a", encoding="utf-8")
        for info in infoList:
            fout.writelines(u'个人主页: ' + base_url + info['home_url']     + "\n")
            fout.writelines(u'姓名: '     + info['title']        + "\n")
            fout.writelines(u'头像地址: ' + info['avatar']       + "\n")
            fout.writelines(u'发言内容: ' + info['content']      + "\n")
            fout.writelines(u'发言时间: ' + info['message_time'] + "\n")
            fout.writelines(u'客户端: '   + info['app']          + "\n")
            fout.writelines("\n\n")
        fout.close()

if __name__ == '__main__':
    myfan = FanFou()
    dataform = myfan.getDataForm()
    infoDict = myfan.getInfo(dataform)
    myfan.savetofile(infoDict)
    print('download complete')

结果如下:(目前只写入 txt, 随后的文章中会使用数据库)

注意, 例子中的用户名和密码不是真实的, 请使用自己的信息进行替换。

result

饭否中的帖子不是一次性加载完的, 也没有分页, 当点击 更多的时候才会加载更多内容, 所以用到了 Ajax 异步加载, 后面的文章会使用这个功能。