使用Python3编撰一个爬虫

优采云 发布时间: 2020-08-23 07:12

  使用Python3编撰一个爬虫

  使用Python3编撰一个爬虫需求简介

  最近厂里有一个新闻采集类的需求,细节大体如下:

  模拟登陆一个外网网站(SSO)抓取新闻(支持代理服务器的形式访问)加工内容款式,以适配手机屏幕将正文中的图片转存到自已的服务器,并替换img标签中的url图片储存服务器须要复用已有的FastDFS分布式文件系统采集结果导出生产库支持日志复印

  初学Python3,正好用这个需求练练手,最后太吃惊的是只用200多行代码就实现了,如果换成Java的话大约须要1200行吧。果然应了那句俗话:人生苦短,我用Python

  登录页面抓包

  第一步其实是抓包,然后再按照抓到的内容,模拟进行HTTP恳求。

  常用的抓包工具,有Mac下的Charles和Windows下的Fiddler。

  它们的原理都是在本机开一个HTTP或SOCKS代理服务器端口,然后将浏览器的代理服务器设置成这个端口,这样浏览器中所有的HTTP恳求就会先经过抓包工具记录出来了。

  这里推荐尽量使用Fiddler,原因是Charles对于cookie的展示是有bug的,举个事例,真实情况:请求A返回了LtpaToken这个cookie,请求B中返回了sid这个cookie。但在Charles中的展示是:请求A中早已同时返回了LtpaToken和sid两个cookie,这就很容易欺骗人了。

  另外Fiddler如今早已有了Linux的Beta版本,貌似是用类似wine的形式实现的。

  如果网站使用了单点登录,可能会涉及到手工生成cookie。所以除了须要剖析每一条HTTP请求的request和response,以及带回去的cookie,还要对页面中的javascript进行剖析,看一下是怎样生成cookie的。

  模拟登陆

  将页面剖析完毕以后,就可以进行模拟HTTP恳求了。

  这里有两个非常好用的第三方库, request 和 BeautifulSoup

  requests 库是拿来替代urllib的,可以十分人性化的的生成HTTP请求,模拟session以及伪造cookie更是便捷。

  BeautifulSoup 用来取代re模块,进行HTML内容解析,可以用tag, class, id来定位想要提取的内容,也支持正则表达式等。

  具体的使用方法直接看官方文档就可以了,写的特别详尽,这里直接给出地址:

  requests官方文档

  BeautifulSoup官方文档

  通过pip3来安装这两个模块:

  sudo apt-get install python3-pip

sudo pip3 install requests

sudo pip3 install beautifulsoup4

  导入模块:

  import requests

from bs4 import BeautifulSoup

  模拟登陆:

  def sso_login():

# 调用单点登录工号认证页面

response = session.post(const.SSO_URL,

data={'login': const.LOGIN_USERNAME, 'password': const.LOGIN_PASSWORD, 'appid': 'np000'})

# 分析页面,取token及ltpa

soup = BeautifulSoup(response.text, 'html.parser')

token = soup.form.input.get('value')

ltpa = soup.form.input.input.input.get('value')

ltpa_value = ltpa.split(';')[0].split('=', 1)[1]

# 手工设置Cookie

session.cookies.set('LtpaToken', ltpa_value, domain='unicom.local', path='/')

# 调用云门户登录页面(2次)

payload = {'token': token}

session.post(const.LOGIN_URL, data=payload, proxies=const.PROXIES)

response = session.post(const.LOGIN_URL, data=payload, proxies=const.PROXIES)

if response.text == "success":

logging.info("登录成功")

return True

else:

logging.info("登录失败")

return False

  这里用到了BeautifulSoup进行HTML解析,取出页面中的token、ltpa等数组。

  然后使用session.cookies.set伪造了一个cookie,注意其中的domain参数,设置成1级域名。

  然后用这个session,去调用网站页面,换回sid这个token。并可以依据页面的返回信息,来简单判定一下成功还是失败。

  列表页面抓取

  登录成功以后,接下来的列表页面抓取就要简单的多了,不考虑分页的话,直接取一个list下来遍历即可。

  def capture_list(list_url):

response = session.get(list_url, proxies=const.PROXIES)

response.encoding = "UTF-8"

soup = BeautifulSoup(response.text, 'html.parser')

news_list = soup.find('div', 'xinwen_list').find_all('a')

news_list.reverse()

logging.info("开始采集")

for news_archor in news_list:

news_cid = news_archor.attrs['href'].split('=')[1]

capture_content(news_cid)

logging.info("结束采集")

  这里使用了response.encoding = "UTF-8"来手工解决乱码问题。

  新闻页面抓取

  新闻页面抓取,涉及到插临时表,这里没有使用每三方库,直接用SQL形式插入。

  其中涉及到款式处理与图片转存,另写一个模块pconvert来实现。

  def capture_content(news_cid):

# 建立DB连接

conn = mysql.connector.connect(user=const.DB_USERNAME, password=const.DB_PASSWORD, host=const.DB_HOST,

port=const.DB_PORT, database=const.DB_DATABASE)

cursor = conn.cursor()

# 判断是否已存在

cursor.execute('select count(*) from material_prepare where news_cid = %s', (news_cid,))

news_count = cursor.fetchone()[0]

if news_count > 0:

logging.info("采集" + news_cid + ':已存在')

else:

logging.info("采集" + news_cid + ':新增')

news_url = const.NEWS_BASE_URL + news_cid

response = session.post(news_url, proxies=const.PROXIES)

response.encoding = "UTF-8"

soup = BeautifulSoup(response.text, 'html.parser')

# logging.info(soup)

news_title = soup.h3.text.strip()[:64]

news_brief = soup.find('div', 'brief').p.text.strip()[:100]

news_author = soup.h5.span.a.text.strip()[:100]

news_content = soup.find('table', 'unis_detail_content').tr.td.prettify()[66:-7].strip()

# 样式处理

news_content = pconvert.convert_style(news_content)

# 将图片转存至DFS并替换URL

news_content = pconvert.convert_img(news_content)

# 入表

cursor.execute(

'INSERT INTO material_prepare (news_cid, title, author, summary, content, add_time, status) VALUES (%s, %s, %s, %s, %s, now(), "0")'

, [news_cid, news_title, news_author, news_brief, news_content])

# 提交

conn.commit()

cursor.close()

  样式处理

  文本式样处理,还是要用到BeautifulSoup,因为原创站点上的新闻内容款式是五花八门的,根据实际情况,一边写一个test函数来生成文本,一边在浏览器上渐渐调试。

  def convert_style(rawtext):

newtext = '' \

+ rawtext + ''

newtext = newtext.replace(' align="center"', '')

soup = BeautifulSoup(newtext, 'html.parser')

img_tags = soup.find_all("img")

for img_tag in img_tags:

del img_tag.parent['style']

return soup.prettify()

  图片转存至DFS

  因为原创站点是在外网中的,采集下来的HTML中,

  标签的地址是外网地址,所以在网段中是诠释不下来的,需要将图片转存,并用新的URL替换原有的URL。

  def convert_img(rawtext):

soup = BeautifulSoup(rawtext, 'html.parser')

img_tags = soup.find_all("img")

for img_tag in img_tags:

raw_img_url = img_tag['src']

dfs_img_url = convert_url(raw_img_url)

img_tag['src'] = dfs_img_url

del img_tag['style']

return soup.prettify()

  图片转存最简单的形式是保存成本地的文件,然后再通过nginx或httpd服务将图片开放出去:

  pic_name = raw_img_url.split('/')[-1]

pic_path = TMP_PATH + '/' + pic_name

with open(pic_path, 'wb') as pic_file:

pic_file.write(pic_content)

  但这儿我们须要复用已有的FastDFS分布式文件系统,要用到它的一个客户端的库fdfs_client-py

  fdfs_client-py不能直接使用pip3安装,需要直接使用一个python3版的源码,并手工更改其中代码。操作过程如下:

  git clone https://github.com/jefforeilly/fdfs_client-py.git

cd dfs_client-py

vi ./fdfs_client/storage_client.py

将第12行 from fdfs_client.sendfile import * 注释掉

python3 setup.py install

sudo pip3 install mutagen

  客户端的使用上没有哪些非常的,直接调用upload_by_buffer,传一个图片的buffer进去就可以了,成功后会返回手动生成的文件名。

  from fdfs_client.client import *

dfs_client = Fdfs_client('conf/dfs.conf')

def convert_url(raw_img_url):

response = requests.get(raw_img_url, proxies=const.PROXIES)

pic_buffer = response.content

pic_ext = raw_img_url.split('.')[-1]

response = dfs_client.upload_by_buffer(pic_buffer, pic_ext)

dfs_img_url = const.DFS_BASE_URL + '/' + response['Remote file_id']

return dfs_img_url

  其中dfs.conf文件中,主要就是配置一下 tracker_server

  日志处理

  这里使用配置文件的形式处理日志,类似JAVA中的log4j吧,首先新建一个log.conf:

  [loggers]

keys=root

[handlers]

keys=stream_handler,file_handler

[formatters]

keys=formatter

[logger_root]

level=DEBUG

handlers=stream_handler,file_handler

[handler_stream_handler]

class=StreamHandler

level=DEBUG

formatter=formatter

args=(sys.stderr,)

[handler_file_handler]

class=FileHandler

level=DEBUG

formatter=formatter

args=('logs/pspider.log','a','utf8')

[formatter_formatter]

format=%(asctime)s %(name)-12s %(levelname)-8s %(message)s

  这里通过配置handlers,可以同时将日志复印到stderr和文件。

  注意args=('logs/pspider.log','a','utf8') 这一行,用来解决文本文件中的英文乱码问题。

  日志初始化:

  import logging

from logging.config import fileConfig

fileConfig('conf/log.conf')

  日志复印:

  logging.info("test")

  完整源码

  到此为止,就是怎样用Python3写一个爬虫的全部过程了。

  采集不同的站点,肯定是要有不同的处理,但方式都是大同小异。

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线