为了家庭,14年辞职从杭州xxx回到湖南老家一个事业单位工作。平时工作量不多,闲暇时间自己捣腾捣腾技术。python一直听闻使用起来比较简单,上手起来也比较快,恰好朋友手头有个项目,项目具体要求如下:
一、客户有个APP,APP成天发一些和他们业务相关的新闻,新闻源头都是来自于各大相关网站,但是由于客户自己平时时间紧凑,没有很多闲暇时间去搜集各大网站他们想要的文章,于是就有了这个需求。客户提供20多个网站网址,每天定时从各大网站抽取他们APP想要的文章。
二、抽取后的文章到APP后台做一个审核功能,审核通过就直接转载到他们APP。
三、文章需要归类,不同文章类型最好在后台归类归好,也方便他们发布和挑选消息。
四、文章需要去重以免影响其挑选速度
之前工作时就看到同事用python工具爬虫去爬取别人网站信息,于是就有了自己也学一下python的兴趣。入门步骤开始,各种大牛绕道,别喷我这菜B了。
一、python安装,直接进入到官网 https://www.python.org/downloads/ 点击download你要的版本,我是下载了3.5最新版本。
二、要抓取别人网站内容,我在网上搜了一下解析html工具,使用 BeautifulSoup ,http://www.crummy.com/software/BeautifulSoup/bs4/download/ 这里可以下载,windows下也可以直接下载tar.gz文件。
三、安装python 略
四、安装BeautifulSoup 模块,用3.5.0就遇到这个坑,在py setup.py build / py setup.py install 之后 在命令行 执行 from bs4 import BeautifulSoup 会报错,看提示说是 叫我重新安装或者 run 2to3 将 bs4从2版本代码转换到3里面也兼容,python在这方面做的比较好。于是我在python安装目录中找到 py ~/tools/scripts/2to3.py -w setup.py
在安装完成之后,一定要关闭掉命令窗口重新打开,要不然死也不会提示成功。
五、可以编码了,打开python编辑器,当然,在抓取别人网站前提先要了解一下这个网站页面的结构,我是要抓取热点新闻,所以从首页下手,发现首页热点dom里面有唯一标记 div的class =centertopnew 和 class=newslist ,于是我可以直接从html中直接把这两个节点提取出来,然后提取出带超链接的标签<a href=xxxx>xxx</a>,把文章名称和文章超链接提取出来保存到本地文件中(刚入门,图简单直接写到了文件,后面肯定会移到数据库中)
#coding=utf-8
import urllib.request
import logging
import urllib.parse
import time
from bs4 import BeautifulSoup
##日志文件
LOG_PATH = "E:/pythonwork/spider/log/spider_log.log"
## 获取页面之后编码转换
PAGE_CODE = "GBK"
##爬虫每爬一个页面在该目录下写一个文件
FILE_BASE_PATH = "E:/pythonwork/spider/files/"
## 模拟火狐浏览器user-agent
U_A = "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)"
HEADERS = {"User-Agent" : U_A }
REQ_URL = "http://news.carnoc.com"
##初始化日志类
logger=logging.getLogger()
logger.setLevel(logging.INFO)
handler=logging.FileHandler(LOG_PATH)
logger.addHandler(handler)
console = logging.StreamHandler()
console.setLevel(logging.DEBUG)
# 设置console日志打印格式
formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s')
console.setFormatter(formatter)
logging.getLogger('').addHandler(console)
values={'wd':'python',
'opt-webpage':'on',
'ie':'gbk'}
url_values=urllib.parse.urlencode(values)
url_values=url_values.encode(encoding='gb2312')
##构建request类
def getRequestClass(url,url_values):
return urllib.request.Request(url,url_values,HEADERS)
##获取网页内容函数
def getHtml(req_url):
try:
response=urllib.request.urlopen(req_url)
except urllib.HTTPError as e:
print('Error code:',e.code)
except URLError as e:
print('Reason',e.reason)
return response.read().decode("GBK")
##写文件函数
def createNewFileAndWrite(file_name,content):
file_obj = open(FILE_BASE_PATH+file_name+".txt","a")
file_obj.write(content)
file_obj.close()
##createNewFileAndWrite("test","中文测试内容")
##根据dom提取文章title和文章超链接
def getTitleAndUrl(newsHTML):
resultList = []
for root in newsHTML:
tempa = root.find_all("a")
for dom in tempa:
kv = []
href = dom.get("href")
title = dom.string
kv.append(title)
kv.append(href)
resultList.append(kv)
return resultList
##循环dom list写入文件中
def writeList(domList,partName):
TIME_FORMAT = "%Y-%m-%d "
now = time.strftime(TIME_FORMAT,time.localtime())
for dom in domList:
createNewFileAndWrite(partName+"-"+now,dom[0]+"\1"+dom[1] + "\n")
## 入口函数
def start():
logging.warn("start get html content ...")
htmlContent = getHtml(getRequestClass(REQ_URL,url_values))
logging.warn("get html success ...")
soup = BeautifulSoup(htmlContent,"html.parser")
logging.warn("write content to txt ...")
##热点新闻 class=centertopnew
writeList(getTitleAndUrl(soup.find_all("div",class_="centertopnew")),"centertopnew")
##各种新闻 class=newslist
writeList(getTitleAndUrl(soup.find_all("div",class_="newslist")),"newslist")
#入口
start()
上面文件保存,本地直接运行就会去网站里面提取首页的热点文章,然后页面里每一块新闻右上角都有“更多”,当然,这是我最想要的,因为在更多里面我可以提取更多我想要的文章。
六、根据首页抓取文章,直接进一步抓取文章内容,并且做深度递归抓取,我暂时设置深度为3,于是有了第二个代码:
#coding=utf-8
## 进入耳机页面进行疯狂的抓取,抓取深度为3
## 配置url规则
import re
import urllib.request
import logging
import urllib.parse
import time
import traceback
import os
from bs4 import BeautifulSoup
DEPTH = 3
##日志文件
LOG_PATH = "E:/pythonwork/spider/log/spider_log.log"
## 获取页面之后编码转换
PAGE_CODE = "GBK"
##爬虫每爬一个页面在该目录下写一个文件
FILE_BASE_PATH = "E:/pythonwork/spider/files/"
## 模拟火狐浏览器user-agent
U_A = "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)"
HEADERS = {"User-Agent" : U_A }
##初始化日志类
logger=logging.getLogger()
logger.setLevel(logging.INFO)
handler=logging.FileHandler(LOG_PATH)
logger.addHandler(handler)
console = logging.StreamHandler()
console.setLevel(logging.DEBUG)
# 设置console日志打印格式
formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s')
console.setFormatter(formatter)
logging.getLogger('').addHandler(console)
linkHashMap = {}
values={'wd':'python',
'opt-webpage':'on',
'ie':'gbk'}
url_values=urllib.parse.urlencode(values)
url_values=url_values.encode(encoding='gb2312')
reg = "^http://news.carnoc.com/{1}[]$"
##读取抓取首页后的文章
def getIndexResult(filepath):
file_obj = open(filepath)
try:
all_content = file_obj.read()
return all_content
except Exception as ex:
print(ex)
finally:
file_obj.close()
p = re.compile(r"http://news.carnoc.com/(cache/)?(list/){1}(\w(/\w)*)+.html")
##深度挖掘数据,暂定为深度为3
def iteratorDrillData(content,depth):
print("*******************当前深度:【"+str(depth)+"】*********************")
if depth > 0 :
contentlist = content.split("\n")
for con in contentlist:
kv = con.split("\1")
try:
if len(kv)>1 and kv[1] :
if "http://news.carnoc.com/" not in kv[1]:
kv[1] = "http://news.carnoc.com/"+kv[1]
logging.info("请求"+kv[1])
if kv[1] in linkHashMap:
break
htmlContent = getHtml(getRequestClass(kv[1],url_values))
if htmlContent :
alist = getTitleAndUrl(htmlContent)
newsBlock = getPageNewsSoup(htmlContent)
## print(newsBlock)
if newsBlock :
fromSite = newsBlock.find_all(id="source_baidu")
publishDate = newsBlock.find_all(id="pubtime_baidu")
author = newsBlock.find_all(id="author_baidu")
##print(newsBlock[0])
newsText = newsBlock.find(id="newstext")
if newsText :
createNewFileAndWrite(mkdirBefore(kv[1][23:len(kv[1])]),str(newsText))
##print(alist)
for m in alist:
if m and m[1] and p.match(m[1]):
try:
print(m)
iteratorDrillData(m[0]+"\1"+m[1],depth-1)
except:
continue
except :
traceback.print_exc()
continue
def mkdirBefore(urlString):
s = urlString.split("/")
# lis = s[0:len(s)-1]
# for i in range(0,len(s)-1):
# if not os.path.exists(FILE_BASE_PATH+lis[i]):
#os.mkdir(FILE_BASE_PATH+lis[i])
#print("创建目录"+lis[i])
print("写入目录文件:"+"html/"+s[-1])
return "html/"+s[-1]
def getRequestClass(url,url_values):
linkHashMap[url] = 2
return urllib.request.Request(url,url_values,HEADERS)
##获取网页内容函数
def getHtml(req_url):
try:
response=urllib.request.urlopen(req_url)
except :
return None
return response.read().decode("GBK")
##写文件函数
def createNewFileAndWrite(file_name,content):
file_obj = open(FILE_BASE_PATH+file_name+".txt","w+")
file_obj.write(content)
file_obj.close()
##根据dom提取文章title和文章超链接
def getTitleAndUrl(newsHTML):
resultList = []
root = BeautifulSoup(newsHTML,"html.parser")
tempa = root.find_all("a")
for dom in tempa:
kv = []
href = dom.get("href")
title = dom.string
kv.append(title)
kv.append(href)
resultList.append(kv)
return resultList
def getPageNewsSoup(htmlContent):
return BeautifulSoup(htmlContent,"html.parser")
##http://news.carnoc.com/(cache/)?(list/){1}(\w)*(/\w)*.html
iteratorDrillData(getIndexResult("E:/pythonwork/spider/files/centertopnew-2015-09-18 .txt"),3)
如果您想直接使用我代码,注意要修改代码中的文件目录,先把目录建好,需要在目录里面新建 log 和html目录,log存放python日志,html存放爬过来的文章内容。当初为了快速上手目的,没有把一切做成全自动的,因为要花一些时间来研究python的函数。
七、上面代码意思是把第一个代码匹配出来的链接请求进去,然后在请求返回结果中提取文章内容(也可能页面没有文章,可能是“更多”,点进去就是文章列表)抽取文章内容也是根据dom的id来提取的。
八、在代码中可以看到我使用了递归,递归深度为3,意思是把当前链接请求进去,然后把返回结果中有超链的链接抠出来继续请求,类似于深度优先,其实个人觉得,在抓取网站时,广度优先要好一点,因为在跑数据的时候,目录一层一层的生成会看得比较清晰当前深度爬到了什么程度!
由于我本地网络原因,请求经常超时,爬了2小时才爬了几百条数据,当然,我这种效率低是理所当然,后期肯定会使用多线程甚至多进程。
由于输入数据和输出数据都可以很类似 “文章标题”,“文章内容”,“文章超链接”,所以同一份代码可以继续拿上一次跑出来的结果继续递归来跑,这种类似广度优先,先把当前页面所有符合要求的超链接全部请求一边,结果全部存起来,然后把全部结果归总之后继续用程序又全部跑一边,又得出结果...如此以来可以直接递归下去(如果文章数很少,并且页面上没有展示出各个文章之间的粘性,这种方式很那把文章爬完),我这种递归要求页面和页面之间有一定的关系,比如该页面有些推荐了另外一个页面,这样就会找到下一个页面了。
八、去重
当前代码去重是直接把url当做key ,每爬一个就放到字典里面去,在爬一个url之前判断一下我有没有爬过,爬过就算了,写文件这里其实也相当于有去重 open('xxx','w') 会直接覆盖掉重名的文件名。我这种去重只适合与单个网站去重,如果把20多个网站数据爬到一起,那么有可能会有问题,比如多个网站使用了phpwind,他们网站url规律都查不到,重复几率较高,不能用这个方式去重,要不然会覆盖掉不应该覆盖的数据。
九、文章归类,还得去研究研究,通过文本内容自动归类如果有做过这种东西的朋友可以在评论里面告知我,我也好学学!
文章写的不好,见谅