Python 爬虫学习笔记

原本想把两三年前学习Python爬虫的笔记整理到一个帖子里,结果发现内容有点多。比如早期学习时记录的一些关于 Python 2 和 3 的区别,去年年底无头浏览器替代 Phantom.js 用来配合 Selenium 爬取。对于一个只想整理笔记的我来说,这算是非常难受的事情了。

XML & Json

1. XML

在处理XML中,Xpath拥有着举足轻重的地位,主要用到的是lxml模块中的 XPath。
  1. 简易使用方法
    from lxml import etree
    selector = etree.HTML(xmlFile/htmlFile)
    selector.xpath(expression) # Return a list
    
  2. XPath的具体使用方法:
    • // 双斜杠 定位根节点,会对全文进行扫描,在文档中选取所有符合条件的内容,以列表的形式返回。
    • / 单斜杠 寻找当前标签路径的下一层路径标签或者对当前路标签内容进行操作
    • /text() 获取当前路径下的文本内容
    • /@xxxx 提取当前路径下标签的属性值
    • | 可选符 使用|可选取若干个路径 如//p | //div 即在当前路径下选取所有符合条件的 p 标签和 div 标签。
    • . 点 用来选取当前节点
    • .. 双点 选取当前节点的父节点
    另外,还有 starts-with(@属性名称,属性字符相同部分),string(.) 两种重要的特殊方法,对应正则表达式中的 Match 和 Search 方法。
  3. XML的处理模式
    • DOM模式:把整个XML读入内存,解析为树,因此占用内存较大,解析慢,优点是可以任意遍历树的节点。
    • SAX流模式:一边读取一边解析,占用内存较小,解析快,缺点是需要自己处理事件

2. Json

对于Json,主要用到的是 Python 库中的 Json 模块,其中主要包含了以下两个函数:
json.dumps():  //对数据进行编码。
json.loads():  //对数据进行解码。
附:Python 编码为 JSON 类型转换对应表:
Python JSON
dict Object
list, tuple Array
str String
int, float, int- & float-derived Enums number
True/False true/false
None null

正则表达式

1. Re库的简单用法

import pyperclip, re

phoneRegex = re.compile(r'''(
    (\d{3}|\(\d{3}\))?
    (\s|-|\.)?
    (\d{3})
    (\s|-|\.)
    (\d{4})
    (\s*(ext|x|ext.)\s*(\d{2,5}))?
)''', re.VERBOSE)

# Email regular expression
emailRegex = re.compile(r'''(
    [a-zA-Z0-9._%+-]+
    @
    [a-zA-Z0-9.-]+
    (\.[a-zA-Z]{2,7})
)''', re.VERBOSE)

# Find matches in clipboard text
text = str(pyperclip.paste())
matches = []
for groups in phoneRegex.findall(text):
    phoneNum = '-'.join([groups[1], groups[3],groups[5]])
    if groups[8] != '':
        phoneNum += ' x' + groups[8]
        matches.append(phoneNum)

for groups in emailRegex.findall(text):
    matches.append(groups[0])

# Copy results to the clipboard

if len (matches) > 0:
    pyperclip.copy('\n'.join(matches))
    print("copied to clipboard")
    print("\n".join(matches))
else:
    print("No phone numbers or email addresses found.")

2.基本匹配规则

  • [0-9]任意一个数字,等价于\d
  • [a-z]任意一个小写字母
  • [A-Z]任意一个大写字母
  • [^0-9]匹配非数字,等价于\D
  • \w 匹配字母数字和下划线,等价于[[^A-Za-z0-9_]
  • \W 等价于 \w 取非
  • . 匹配任意字符
  • [] 匹配内部任意字符或子表达式
  • [^]对字符集合取非

3. 基本匹配模式

  • *匹配前面的字符或表达式 0 次或多次
  • +匹配前面的字符至少一次
  • ?匹配前面的字符 0 次或多次
  • ^匹配字符串开头
  • $匹配字符串结束

常用的Python爬虫库(整理中)

1. Selenium

Selenium 常用于网站 TDD(Test-Driven Development,测试驱动开发)和办公自动化(?)。注意,Windows环境下需要单独安装 [Browser]driver.exe才能使用
注意 :和 TDD 相同,使用Selenium时善于使用 Try & Except 而不是 If & Else
在爬虫中经常使用的是以下两条:
find_element(s)_by_tag_name
find_element(s)_by_css_selector

2. Request(核心库)

  1. 简易使用方法
    import requests
    #============================#
    url = 'http://blog.cubat.cc'
    r = requests.get(url)
    print(r.encoding)
    print(r.status_code)
    #============================#
    params = {'k1':'v1', 'k2':'v2'}
    r =requests.get('http://blog.cubat.cc/', params)
    print(r.url)
    
    这里主要给出的是抓取整个页面并返回编码和状态码,传入参数的例子。
  2. 抓取图片
    import requests
    from io import BytesIO
    from PIL import Image
    #============================#
    url = 'http://blog.cubat.cc'
    r = requests.get('http://cubat.cn/static/img/girigiri.gif')
    # Method A
    image = Image.open(BytesIO(r.content))
    image.save('1.jpg')
    # Method B
    with open('2.jpg','wb+') as f:
     for chunk in r.iter_content(1024):
         f.write(chunk)
    
    关于抓取图片,需要Pillow中的图片库和自带的二进制库。值得提醒的是,使用 with 后不管 with 中的代码出现什么错误,都会进行对当前对象进行清理工作。通常情况下,with 经常配合 as 添加别名,方便操作。
  3. 获取网站Cookie
    import requests
    url = 'http://blog.cubat.cc'
    r = requests.get(url)
    cookies = r.cookies
    for k, v in cookies.get_dict().items():
     print(k, v)
    
    4.重定向和重定向历史
    import requests
    #============================#
    url = 'http://google.com'
    r = requests.head(url, allow_redirects = True)
    print(r.url)
    print(r.status_code)
    print(r.history)
    

3. Beautiful Soup 4

  1. 简易使用方法
    from bs4 import BeautifulSoup
    import requests
    #============================#
    url = 'https://google.com/'
    wb_data = requests.get(url)
    #============================#
    soup = BeautifulSoup(wb_data.text, 'lxml')
    print(soup)
    
  2. 设置代理的套路
    proxy_list = {
    'http://address:port'
    'http://address:port'
    'http://address:port'
    }
    proxy_ip = random.choice(proxy_list)
    proxies = {'http':proxy_ip}
    

4. Pyquery

如果你觉得用 Requests 配合正则太麻烦,BeautifulSoup 的语法太多太难记,恰好掌握了 JQuery 那么就可以试试这一个。
用法和 BeautifulSoup 差不多,更多用法可以查看 Pyquery – PyQuery complete API

5. Urllib (内置库)

Urllib 是 Python 自带的一个标准库,在模拟登陆时,常常是 Urllib + Cookielib 配合使用。Urllib(number) 通常是当前版本 Python 的 Urllib 的补充库。例如 Urllib2 就是 Python2 的 Urllib 的补充。Python3 中的 Urllib 整合了 Urllib(Python2) + Urllib2,在概念上它的 Urllib.request == Urllib(Python2),具体区别可以阅读这个 Cheat Sheet
基本用法:
import socket
from urllib import request, error, parse
#===================================#
url = '[RESTFUL_URL]'
headers = {
    'User-Agent':'[USER_AGENT]',
    'Host':'[DOMAIN]'
}
dict = {
    'name':'[NAME]'
}
#===================================#
data = bytes(parse.urlencode(dict), encoding='utf8')
req = request.Request(url=url, data=data, headers=headers, method='POST')
try:
    response = request.urlopen(req, timeout=1)
    print(response.read().decode('utf-8'))
except error.URLError as e:
    if isinstance(e.reason, socket.timeout):
        print("Time Out")
附录:Python 内置的 HTTP 请求库
库名称 主要作用
urllib.request 请求模块
urllib.error 异常处理模块
urllib.parse URL 解析模块
urllib.urlencode URL 拼接模块
urllib.robotparser robots.txt 文件解析模块
其中,Urllib.parse 主要用于 URL 的拆分和拼接,在处理 URL 时非常方便。当链接后缀需要填入参数时,可以考虑使用 urlencode(例如请求百度搜索),其使用字典进行存储,使用起来非常方便。

个人常见问题

  • 报错 NotImplementedError
    NotImplementedError: Only the following pseudo-classes are implemented: nth-of-type.
    
    解决方法:
    Soup.select 中的 nth-child 改为 nth-of-type 即可。
    多说一句,Beautiful 支持使用 lxml 解析器,但是却不支持 nth-child 就有点奇怪,不过好在 nth-of-type 担当了其功能,因此选择一个元素下的并列标签只能使用 nth-of-type ,例如:
    tags = release_dates = soup.select('div[class="media-body"] > p:nth-of-type(2) > span:nth-of-type(2)')
    
  • 报错 Requests : No connection adapters were found for
    for single_url in urls:
     your_main_func(single_url)
    
    这种问题主要是因为获取所有链接后没有提取出单独的数组,当然如果 Requests 获取链接没有包含协议时同样会出现这个错误,这时候需要在链接头加入 http:// 或者 https://
  • 一行代码内打印所有月份,不足两位数向左侧补零
    urls = ['https://blog.cubat.cc/2018/{0:0>2}'.format(int(i)) for i in range(1, 13)]
    
    千万不要用zfill方法和for循环返回数据,别问我怎么知道的…
  • Css Selector 写得太长了
    如果已经确定了元素的唯一性,可以使用[id_name][Space][div_name][dot][class] 替代 >的具体子目录,减少输入内容。
  • 页面中的元素某些原因没有内容,该如何判断
    imgs = soup.select('img[class="media-object"]') if soup.find_all('img', 'media-object') else None
    
  • ‘NoneType’ object is not callable
    当我们获取链接的时候,通常有些链接是需要处理的,已知 BS4 获取的元素均为 Tag 类型,那么需要将其转化为字符串后再进行判断,即 if [Tag].text.startswith(('[judge_a]','[judge_b]')) 或者只比较链接 if [Tag].get('href').startswith('[judge]'):
  • 判断页面是否有某种元素
    if soup.find('h3','site-name'): # Do Something!
    else: pass
    
    其中,h3 为 HTML 中的标签,site-name 为标签中的 Class。
    find函数简易说明find(name=,attrs=,recursive=,text=,**wargs=)
  • 将 BS4 格式的 Tag 中的 JSON 文件格式化
    titles = json.loads((soup.select("p")[-1]).text)
    
  • 判断是否跳转到下一页
    获取文章列表类下的所有标签,这样会生成一个列表。字符串化列表的最后一个元素,判断元素编号和上一页最后一个元素所在列表位置是否相等即可得知是否需要跳转到下一页。
  • 如何断点续爬
    使用爬虫爬取所有的链接后再对页面进行依次爬去。设置一个获取数据库最大项,然后每爬一页计数器加一。最后让这个值在数据库里做查找即可。
  • 如何下载图片(使用 Python 读写文件的方法)
    import requests
    response = requests.get('[FILE_URI]')
    with open('LOCAL_FILENAME.jpg', 'wb') as f:
    f.write(response.content)
    f.close()
    
    除此之外,也可以使用urlretrive方法。
  • 解决 JS 渲染造成的获取不全
    1. 分析页面的 Ajax 请求,解析 JSON 文件
    2. Selenium + Webdriver + PhantomJS (Headless browser)
    3. Splash
    4. PyV8, Ghost.py

爬虫中开启多线程

from multiprocessing import Pool
from .pageParsing import get_links_from,get_game_sort
#===================================#
def get_all_links_from(basic_uri):
    for years in range(2003, 2016):
        for months in range(1,13):
            get_links_from(basic_uri,months,years)
#===================================#
if __name__ == '__main__':
    pool = Pool(processes=16)
    pool.map(get_links_from, get_game_sort.split())
其中,Pool可以指定使用 CPU 的核心数,map 用于多函数迭代执行。

爬虫中常用的算法(整理中)

参考资料

评论