Browse Source

CCF化纤信息

hsun 10 months ago
parent
commit
168691e634
11 changed files with 3211 additions and 36 deletions
  1. 3 1
      .gitignore
  2. BIN
      eta_data_analysis
  3. 6 13
      go.mod
  4. 15 22
      go.sum
  5. 724 0
      services/base_from_ccf/common.go
  6. 1333 0
      services/base_from_ccf/edb.go
  7. 215 0
      services/base_from_ccf/stock.go
  8. 19 0
      services/task.go
  9. 880 0
      static/ccf_data_rule.json
  10. 14 0
      utils/config.go
  11. 2 0
      utils/constants.go

+ 3 - 1
.gitignore

@@ -8,4 +8,6 @@ binlog/
 /static/imgs/python/*
 *.gz
 *.exe
-eta_data_analysis
+eta_data_analysis
+static/ccf
+ccf_cookie.txt

BIN
eta_data_analysis


+ 6 - 13
go.mod

@@ -3,6 +3,7 @@ module eta/eta_data_analysis
 go 1.19
 
 require (
+	github.com/PuerkitoBio/goquery v1.9.2
 	github.com/beego/bee/v2 v2.1.0
 	github.com/beego/beego/v2 v2.1.4
 	github.com/gonum/stat v0.0.0-20181125101827-41a0da705a5b
@@ -12,14 +13,14 @@ require (
 	github.com/shakinm/xlsReader v0.9.12
 	github.com/shopspring/decimal v1.3.1
 	github.com/tealeg/xlsx v1.0.5
-	github.com/tealeg/xlsx/v3 v3.3.5
 	github.com/xuri/excelize/v2 v2.8.0
+	golang.org/x/net v0.24.0
 )
 
 require (
+	github.com/andybalholm/cascadia v1.3.2 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/cespare/xxhash/v2 v2.2.0 // indirect
-	github.com/frankban/quicktest v1.14.6 // indirect
 	github.com/golang/protobuf v1.5.3 // indirect
 	github.com/gonum/blas v0.0.0-20181208220705-f22b278b28ac // indirect
 	github.com/gonum/floats v0.0.0-20181209220543-c233463c7e82 // indirect
@@ -27,16 +28,12 @@ require (
 	github.com/gonum/internal v0.0.0-20181124074243-f884aa714029 // indirect
 	github.com/gonum/lapack v0.0.0-20181123203213-e4cdc5a0bff9 // indirect
 	github.com/gonum/matrix v0.0.0-20181209220409-c518dec07be9 // indirect
-	github.com/google/btree v1.0.0 // indirect
-	github.com/google/go-cmp v0.5.9 // indirect
 	github.com/hashicorp/golang-lru v0.5.4 // indirect
-	github.com/kr/pretty v0.3.1 // indirect
 	github.com/kr/text v0.2.0 // indirect
 	github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
 	github.com/metakeule/fmtdate v1.1.2 // indirect
 	github.com/mitchellh/mapstructure v1.5.0 // indirect
 	github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
-	github.com/peterbourgon/diskv/v3 v3.0.1 // indirect
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/prometheus/client_golang v1.16.0 // indirect
 	github.com/prometheus/client_model v0.3.0 // indirect
@@ -44,16 +41,12 @@ require (
 	github.com/prometheus/procfs v0.10.1 // indirect
 	github.com/richardlehane/mscfb v1.0.4 // indirect
 	github.com/richardlehane/msoleps v1.0.3 // indirect
-	github.com/rogpeppe/fastuuid v1.2.0 // indirect
-	github.com/rogpeppe/go-internal v1.10.0 // indirect
-	github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa // indirect
 	github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect
 	github.com/xuri/efp v0.0.0-20230802181842-ad255f2331ca // indirect
 	github.com/xuri/nfp v0.0.0-20230819163627-dc951e3ffe1a // indirect
-	golang.org/x/crypto v0.12.0 // indirect
-	golang.org/x/net v0.14.0 // indirect
-	golang.org/x/sys v0.11.0 // indirect
-	golang.org/x/text v0.12.0 // indirect
+	golang.org/x/crypto v0.22.0 // indirect
+	golang.org/x/sys v0.19.0 // indirect
+	golang.org/x/text v0.14.0 // indirect
 	google.golang.org/protobuf v1.30.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )

+ 15 - 22
go.sum

@@ -1,3 +1,7 @@
+github.com/PuerkitoBio/goquery v1.9.2 h1:4/wZksC3KgkQw7SQgkKotmKljk0M6V8TUvA8Wb4yPeE=
+github.com/PuerkitoBio/goquery v1.9.2/go.mod h1:GHPCaP0ODyyxqcNoFGYlAprUFH81NuRPd0GX3Zu2Mvk=
+github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
+github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
 github.com/beego/bee/v2 v2.1.0 h1:4WngbAnkvVOyKy74WXcRH3clon76wkjhuzrV2mx2fQU=
 github.com/beego/bee/v2 v2.1.0/go.mod h1:wDhKy5TNxv46LHKsK2gyxo38ObCOm9PbCN89lWHK3EU=
 github.com/beego/beego/v2 v2.1.4 h1:99d8+sUmQyfaArQIjjzWGE+KYU9M1GkfWec74nXQxFY=
@@ -11,8 +15,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/elazarl/go-bindata-assetfs v1.0.1 h1:m0kkaHRKEu7tUIUFVwhGGGYClXvyl4RE03qmvRTNfbw=
-github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
-github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
@@ -32,16 +34,12 @@ github.com/gonum/matrix v0.0.0-20181209220409-c518dec07be9 h1:V2IgdyerlBa/MxaEFR
 github.com/gonum/matrix v0.0.0-20181209220409-c518dec07be9/go.mod h1:0EXg4mc1CNP0HCqCz+K4ts155PXIlUywf0wqN+GfPZw=
 github.com/gonum/stat v0.0.0-20181125101827-41a0da705a5b h1:fbskpz/cPqWH8VqkQ7LJghFkl2KPAiIFUHrTJ2O3RGk=
 github.com/gonum/stat v0.0.0-20181125101827-41a0da705a5b/go.mod h1:Z4GIJBJO3Wa4gD4vbwQxXXZ+WHmW6E9ixmNrwvs0iZs=
-github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
-github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
-github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
 github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
-github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -58,12 +56,8 @@ github.com/mozillazg/go-pinyin v0.20.0 h1:BtR3DsxpApHfKReaPO1fCqF4pThRwH9uwvXzm+
 github.com/mozillazg/go-pinyin v0.20.0/go.mod h1:iR4EnMMRXkfpFVV5FMi4FNB6wGq9NV6uDWbUuPhP4Yc=
 github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
 github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
-github.com/peterbourgon/diskv/v3 v3.0.1 h1:x06SQA46+PKIUftmEujdwSEpIx8kR+M9eLYsUxeYveU=
-github.com/peterbourgon/diskv/v3 v3.0.1/go.mod h1:kJ5Ny7vLdARGU3WUuy6uzO6T0nb/2gWcT1JiBvRmb5o=
-github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/profile v1.5.0 h1:042Buzk+NhDI+DeSAA62RwJL8VAuZUMQZUjCsRz1Mug=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
@@ -81,13 +75,7 @@ github.com/richardlehane/msoleps v1.0.3 h1:aznSZzrwYRl3rLKRT3gUk9am7T/mLNSnJINvN
 github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
 github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
 github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
-github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s=
-github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
-github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
 github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
-github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
-github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa h1:2cO3RojjYl3hVTbEvJVqrMaFmORhL6O06qdW42toftk=
-github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa/go.mod h1:Yjr3bdWaVWyME1kha7X0jsz3k2DgXNa1Pj3XGyUAbx8=
 github.com/shakinm/xlsReader v0.9.12 h1:F6GWYtCzfzQqdIuqZJ0MU3YJ7uwH1ofJtmTKyWmANQk=
 github.com/shakinm/xlsReader v0.9.12/go.mod h1:ME9pqIGf+547L4aE4YTZzwmhsij+5K9dR+k84OO6WSs=
 github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 h1:DAYUYH5869yV94zvCES9F51oYtN5oGlwjxJJz7ZCnik=
@@ -101,8 +89,6 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
 github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
 github.com/tealeg/xlsx v1.0.5 h1:+f8oFmvY8Gw1iUXzPk+kz+4GpbDZPK1FhPiQRd+ypgE=
 github.com/tealeg/xlsx v1.0.5/go.mod h1:btRS8dz54TDnvKNosuAqxrM1QgN1udgk9O34bDCnORM=
-github.com/tealeg/xlsx/v3 v3.3.5 h1:dzmns01jRf0SveBe7VqkcO2LCLOcypcDI6H66PiZycQ=
-github.com/tealeg/xlsx/v3 v3.3.5/go.mod h1:KV4FTFtvGy0TBlOivJLZu/YNZk6e0Qtk7eOSglWksuA=
 github.com/xuri/efp v0.0.0-20230802181842-ad255f2331ca h1:uvPMDVyP7PXMMioYdyPH+0O+Ta/UO1WFfNYMO3Wz0eg=
 github.com/xuri/efp v0.0.0-20230802181842-ad255f2331ca/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
 github.com/xuri/excelize/v2 v2.8.0 h1:Vd4Qy809fupgp1v7X+nCS/MioeQmYVVzi495UCTqB7U=
@@ -112,8 +98,9 @@ github.com/xuri/nfp v0.0.0-20230819163627-dc951e3ffe1a/go.mod h1:WwHg+CVyzlv/TX9
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
 golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
+golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
+golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
 golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo=
 golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@@ -122,9 +109,11 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
-golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
 golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
+golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
+golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -135,12 +124,15 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
 golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
+golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
 golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
 golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -149,8 +141,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
-golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
 golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=

+ 724 - 0
services/base_from_ccf/common.go

@@ -0,0 +1,724 @@
+package base_from_ccf
+
+import (
+	"bytes"
+	"compress/gzip"
+	"encoding/json"
+	"eta/eta_data_analysis/utils"
+	"fmt"
+	"github.com/PuerkitoBio/goquery"
+	"golang.org/x/net/html/charset"
+	"golang.org/x/text/encoding/simplifiedchinese"
+	"golang.org/x/text/transform"
+	"io/ioutil"
+	"net/http"
+	"net/url"
+	"os"
+	"path/filepath"
+	"regexp"
+	"strconv"
+	"strings"
+	"time"
+)
+
+const (
+	CCFSearchPageUrl       = "https://www.ccf.com.cn/newscenter/simplesearch.php" // CCF搜索页地址
+	CCFReportDetailBaseUrl = "https://www.ccf.com.cn"                             // CCF报告详情页地址
+)
+
+// postEdbLib 调用指标接口
+func postEdbLib(param map[string]interface{}, method string) (result []byte, err error) {
+	postUrl := utils.EDB_LIB_URL + method
+	postData, err := json.Marshal(param)
+	if err != nil {
+		return
+	}
+	result, err = httpPost(postUrl, string(postData), "application/json")
+	if err != nil {
+		return
+	}
+	return
+}
+
+// httpPost HTTP请求
+func httpPost(url, postData string, params ...string) ([]byte, error) {
+	fmt.Println("httpPost Url:" + url)
+	body := ioutil.NopCloser(strings.NewReader(postData))
+	client := &http.Client{}
+	req, err := http.NewRequest("POST", url, body)
+	if err != nil {
+		return nil, err
+	}
+	contentType := "application/x-www-form-urlencoded;charset=utf-8"
+	if len(params) > 0 && params[0] != "" {
+		contentType = params[0]
+	}
+	req.Header.Set("Content-Type", contentType)
+	req.Header.Set("authorization", utils.MD5(utils.APP_EDB_LIB_NAME_EN+utils.EDB_LIB_Md5_KEY))
+	resp, err := client.Do(req)
+	if err != nil {
+		fmt.Println("client.Do err:" + err.Error())
+		return nil, err
+	}
+	defer resp.Body.Close()
+	b, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		fmt.Println("httpPost:" + string(b))
+	}
+	return b, err
+}
+
+// fetchPageHtml 获取网站HTML文本
+func fetchPageHtml(baseUrl string) (respBody []byte, err error) {
+	defer func() {
+		if err != nil {
+			tips := fmt.Sprintf("BuildCCFRequest ErrMsg: %s", err.Error())
+			utils.FileLog.Info(tips)
+			fmt.Println(tips)
+		}
+	}()
+	if baseUrl == "" {
+		err = fmt.Errorf("CCF请求地址为空")
+		return
+	}
+
+	// 读取Cookie
+	if utils.CCFCookieFile == "" {
+		err = fmt.Errorf("cookie文件未配置")
+		return
+	}
+	cookieByte, e := ioutil.ReadFile(utils.CCFCookieFile)
+	if e != nil {
+		err = fmt.Errorf("读取cookie文件失败, err: %s", e.Error())
+		return
+	}
+	strCookie := strings.TrimSpace(string(cookieByte))
+	if strCookie == "" {
+		err = fmt.Errorf("cookie为空")
+		return
+	}
+
+	// 拉取网站内容
+	cli := new(http.Client)
+	req, e := http.NewRequest("GET", baseUrl, nil)
+	if e != nil {
+		err = fmt.Errorf("")
+		return
+	}
+
+	req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7")
+	req.Header.Set("Accept-Encoding", "gzip, deflate, br")
+	req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6")
+	req.Header.Set("Connection", "keep-alive")
+	req.Header.Set("Cookie", strCookie)
+	req.Header.Set("Host", "www.ccf.com.cn")
+	req.Header.Set("Referer", baseUrl)
+	req.Header.Set("Sec-Ch-Ua", "\"Not A(Brand\";v=\"99\", \"Microsoft Edge\";v=\"121\", \"Chromium\";v=\"121\"")
+	req.Header.Set("Sec-Ch-Ua-Mobile", "?0")
+	req.Header.Set("Sec-Ch-Ua-Platform", "\"Windows\"")
+	req.Header.Set("Sec-Fetch-Dest", "empty")
+	req.Header.Set("Sec-Fetch-Mode", "cors")
+	req.Header.Set("Sec-Fetch-Site", "same-origin")
+	req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0")
+	req.Header.Set("X-Requested-With", "XMLHttpRequest")
+
+	resp, e := cli.Do(req)
+	if e != nil {
+		err = fmt.Errorf("HTTP client Do err: %s", e.Error())
+		return
+	}
+	defer func() {
+		_ = resp.Body.Close()
+	}()
+
+	// 读取响应的内容
+	reader, e := gzip.NewReader(resp.Body)
+	if e != nil {
+		err = fmt.Errorf("gzip NewReader err: %s", e.Error())
+		return
+	}
+	body, e := ioutil.ReadAll(reader)
+	if e != nil {
+		err = fmt.Errorf("read body err: %s", e.Error())
+		return
+	}
+
+	// 转换编码
+	utf8Reader, e := charset.NewReaderLabel("gb2312", bytes.NewReader(body))
+	if e != nil {
+		err = fmt.Errorf("utf8 reader err: %s", e.Error())
+		return
+	}
+	utf8Body, e := ioutil.ReadAll(utf8Reader)
+	if e != nil {
+		err = fmt.Errorf("utf8 body err: %s", e.Error())
+		return
+	}
+	respBody = utf8Body
+	return
+}
+
+// DataRule 数据爬取规则
+type DataRule struct {
+	Name      string `json:"Name"`
+	Frequency string `json:"Frequency"`
+	PageDir   string `json:"PageDir"`
+	Search    struct {
+		ClassId      string `json:"ClassId"`
+		SubClassId   string `json:"SubClassId"`
+		ProductId    string `json:"ProductId"`
+		SubProductId string `json:"SubProductId"`
+		SimpleTerms  string `json:"SimpleTerms"`
+	} `json:"Search"`
+	TableFetch []struct {
+		Keyword string `json:"Keyword"`
+		Unit    string `json:"Unit"`
+	} `json:"TableFetch"`
+	EdbMatch   []DataRuleEdbMatch `json:"EdbMatch"`
+	StockTable struct {
+		ClassifyId int `json:"ClassifyId"`
+	} `json:"StockTable"`
+}
+
+// DataRuleEdbMatch 数据爬取规则-指标匹配
+type DataRuleEdbMatch struct {
+	IndexCode  string `json:"IndexCode"`
+	IndexName  string `json:"IndexName"`
+	ClassifyId int    `json:"ClassifyId"`
+	Frequency  string `json:"Frequency"`
+	Product    string `json:"Product"`
+	Market     string `json:"Market"`
+	MatchUnit  string `json:"MatchUnit" description:"匹配单位"`
+	Unit       string `json:"Unit" description:"实际单位"`
+}
+
+// loadDataRule 从配置中读取爬取规则
+func loadDataRule(nameKey string) (fetchRule *DataRule, err error) {
+	if utils.CCFDataRuleFile == "" {
+		err = fmt.Errorf("rule文件不存在")
+		return
+	}
+	b, e := ioutil.ReadFile(utils.CCFDataRuleFile)
+	if e != nil {
+		err = fmt.Errorf("读取rule文件失败, err: %v", e)
+		return
+	}
+	rules := make([]*DataRule, 0)
+	if e = json.Unmarshal(b, &rules); e != nil {
+		err = fmt.Errorf("解析rule文件失败, err: %v", e)
+		return
+	}
+	for _, v := range rules {
+		if v.Name != "" && v.Name == nameKey {
+			fetchRule = v
+			return
+		}
+	}
+	err = fmt.Errorf("rule不存在, nameKey: %s", nameKey)
+	return
+}
+
+// savePageHtml 拉取历史报告详情
+func savePageHtml(nameKey, saveDir string, historyPage bool, reportMax int) (files []string, err error) {
+	if nameKey == "" {
+		return
+	}
+	defer func() {
+		if err != nil {
+			tips := fmt.Sprintf("GetCCFOilEdbHistory ErrMsg: %s", err.Error())
+			utils.FileLog.Info(tips)
+			fmt.Println(tips)
+		}
+	}()
+	fetchRule, e := loadDataRule(nameKey)
+	if e != nil {
+		err = fmt.Errorf("loadDataRule, err: %v", e)
+		return
+	}
+	if saveDir == "" {
+		saveDir = "static/ccf"
+	}
+
+	// 获取品种第一页
+	baseUrl := fmt.Sprintf(`%s?newssubmit=1&sitename=localhost`, CCFSearchPageUrl)
+	if fetchRule.Search.ClassId != "" {
+		baseUrl = fmt.Sprintf(`%s&ClassID=%s`, baseUrl, fetchRule.Search.ClassId)
+	}
+	if fetchRule.Search.SubClassId != "" {
+		baseUrl = fmt.Sprintf(`%s&SubClassID=%s`, baseUrl, fetchRule.Search.SubClassId)
+	}
+	if fetchRule.Search.ProductId != "" {
+		baseUrl = fmt.Sprintf(`%s&ProductID=%s`, baseUrl, fetchRule.Search.ProductId)
+	}
+	if fetchRule.Search.SubProductId != "" {
+		baseUrl = fmt.Sprintf(`%s&ProductID=%s`, baseUrl, fetchRule.Search.SubProductId)
+	}
+	if fetchRule.Search.SimpleTerms != "" {
+		termsEncode, e := gb2312ToPercentEncoding(fetchRule.Search.SimpleTerms)
+		if e != nil {
+			err = fmt.Errorf("gb2312ToPercentEncoding err: %v", e)
+			return
+		}
+		baseUrl = fmt.Sprintf(`%s&simpleterms=%s`, baseUrl, termsEncode)
+	}
+	firstPage := fmt.Sprintf(`%s&cur_pg_num=%d`, baseUrl, 1)
+
+	// 首页报告链接
+	firstHtml, e := fetchPageHtml(firstPage)
+	if e != nil {
+		err = fmt.Errorf("获取首页HTML失败, err: %v", e)
+		return
+	}
+	firstHrefs, e := analysisReportHrefs(firstHtml, 1)
+	if e != nil {
+		err = fmt.Errorf("读取首页列表报告链接失败, err: %v", e)
+		return
+	}
+	var historyHrefs []ReportHrefs
+	historyHrefs = append(historyHrefs, firstHrefs...)
+
+	ticker := time.NewTicker(5 * time.Second)
+	defer ticker.Stop()
+
+	// 历史报告
+	if historyPage {
+		endPage, e := analysisEndPage(firstHtml)
+		if e != nil {
+			err = fmt.Errorf("解析首页最后页码失败, err: %v", e)
+			return
+		}
+		if endPage > 1 {
+			for i := 2; i <= endPage; i++ {
+				<-ticker.C
+				fmt.Printf("开始读取历史页%d\n", i)
+
+				// 每页28条数据, 需要带上页码*28的偏移量不然始终获取第一页
+				pageUrl := fmt.Sprintf(`%s&cur_pg_num=%d&cur_row_pos=%d`, baseUrl, i, i*28)
+				fmt.Println("pageUrl: ", pageUrl)
+				pageContents, e := fetchPageHtml(pageUrl)
+				if e != nil {
+					err = fmt.Errorf("获取首页HTML失败, err: %v", e)
+					return
+				}
+				pageHrefs, e := analysisReportHrefs(pageContents, i)
+				if e != nil {
+					err = fmt.Errorf("读取第%d页列表报告链接失败, err: %v", i, e)
+					return
+				}
+				historyHrefs = append(historyHrefs, pageHrefs...)
+				fmt.Printf("结束读取历史页%d\n", i)
+			}
+		}
+		fmt.Println("endPage: ", endPage)
+	}
+	fmt.Println("historyHrefs len: ", len(historyHrefs))
+	fmt.Println("historyHrefs: ", historyHrefs)
+
+	// 拉取报告留档
+	strDate := time.Now().Format("20060102")
+	reportCount := 0
+	for _, v := range historyHrefs {
+		<-ticker.C
+
+		if reportMax > 0 {
+			reportCount += 1
+			if reportCount > reportMax {
+				break
+			}
+		}
+		fmt.Printf("拉取报告: %s; url: %s\n", v.Title, v.Href)
+
+		htm, e := fetchPageHtml(fmt.Sprintf("%s%s", CCFReportDetailBaseUrl, v.Href))
+		if e != nil {
+			err = fmt.Errorf("获取首页HTML失败, err: %v", e)
+			return
+		}
+		outputPath := fmt.Sprintf("%s/%d-%s-%s.html", saveDir, v.Page, strDate, v.Title)
+		if e = writeHTMLToFile(string(htm), outputPath); e != nil {
+			fmt.Printf("写入html出错, err: %v", e)
+			continue
+		}
+		files = append(files, outputPath)
+	}
+	fmt.Println("拉取报告 end")
+	return
+}
+
+// analysisEndPage 读取列表页最后一页页码
+func analysisEndPage(contents []byte) (endPage int, err error) {
+	doc, e := goquery.NewDocumentFromReader(strings.NewReader(string(contents)))
+	if e != nil {
+		err = fmt.Errorf("NewDocumentFromReader err: %v", e)
+		return
+	}
+
+	// 查找页码元素并遍历a标签
+	sectionDigg := doc.Find(".digg")
+	aElements := sectionDigg.Find("a")
+
+	// 获取倒数第二个a标签中的页码
+	totalAElements := aElements.Length()
+	targetIndex := totalAElements - 2
+	if targetIndex >= 0 && targetIndex < totalAElements {
+		targetA := aElements.Eq(targetIndex)
+		txt := targetA.Text()
+		endPage, e = strconv.Atoi(txt)
+		if e != nil {
+			err = fmt.Errorf("页码文本有误, %s", txt)
+			return
+		}
+		fmt.Println(endPage)
+		return
+	}
+	endPage = 1
+	return
+}
+
+// ReportHrefs 报告链接
+type ReportHrefs struct {
+	Title string `description:"报告标题"`
+	Href  string `description:"报告详情链接"`
+	Page  int    `description:"页码"`
+}
+
+// analysisReportHrefs 解析列表页报告链接
+func analysisReportHrefs(contents []byte, page int) (hrefs []ReportHrefs, err error) {
+	doc, e := goquery.NewDocumentFromReader(strings.NewReader(string(contents)))
+	if e != nil {
+		err = fmt.Errorf("NewDocumentFromReader err: %v", e)
+		return
+	}
+	doc.Find("ul.newslist li a").Each(func(_ int, s *goquery.Selection) {
+		href, exists := s.Attr("href")
+		if exists {
+			title := s.Text()
+			hrefs = append(hrefs, ReportHrefs{
+				Title: title,
+				Href:  href,
+				Page:  page,
+			})
+		}
+	})
+	return
+}
+
+// writeHTMLToFile 将HTML内容写入指定的文件中
+func writeHTMLToFile(content string, filePath string) error {
+	// 使用os.Create创建文件,如果文件已存在则会被截断
+	file, err := os.Create(filePath)
+	if err != nil {
+		return err
+	}
+	defer file.Close()
+
+	// 将HTML内容写入文件
+	_, err = file.WriteString(content)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// extractReportPublishTime 提取报告发布时间
+func extractReportPublishTime(text string) (time.Time, error) {
+	datePattern := `(\d{4}年\d{1,2}月\d{1,2}日\d{1,2}:\d{2})`
+	re := regexp.MustCompile(datePattern)
+
+	var strTime string
+	match := re.FindStringSubmatch(text)
+	if len(match) <= 0 {
+		return time.Time{}, fmt.Errorf("没有读取出日期")
+	}
+	strTime = match[0]
+
+	// 转为时间格式
+	dateFormat := "2006年01月02日15:04"
+	parsedDate, e := time.ParseInLocation(dateFormat, strTime, time.Local)
+	if e != nil {
+		return time.Time{}, fmt.Errorf("日期转换失败, str: %s, err: %v", strTime, e)
+	}
+	return parsedDate, nil
+}
+
+// calculateDataHalfVal 取出数据区间的折中值, 如"7-9天"返回结果为"8"
+func calculateDataHalfVal(duration string) (result string, err error) {
+	re := regexp.MustCompile(`\d+`)
+	matches := re.FindAllString(duration, -1)
+	if len(matches) != 2 {
+		err = fmt.Errorf("未找到两个数字, Num: %d", len(matches))
+		return
+	}
+
+	a, e := strconv.Atoi(matches[0])
+	if e != nil {
+		err = e
+		return
+	}
+	b, e := strconv.Atoi(matches[1])
+	if e != nil {
+		err = e
+		return
+	}
+	average := float64(a+b) / 2.0
+
+	// 格式化结果
+	if average == float64(int(average)) {
+		result = strconv.Itoa(int(average))
+	} else {
+		result = fmt.Sprintf("%.1f", average)
+	}
+	return
+}
+
+// gb2312ToPercentEncoding 中文字符转码
+func gb2312ToPercentEncoding(input string) (string, error) {
+	// 创建GB18030编码转换器(兼容GB2312)
+	encoder := simplifiedchinese.GB18030.NewEncoder()
+
+	// 使用转换器将字符串转换为GB18030编码的字节流,并写入bytes.Buffer
+	var buf bytes.Buffer
+	writer := transform.NewWriter(&buf, encoder)
+	_, err := writer.Write([]byte(input))
+	if err != nil {
+		return "", err
+	}
+	err = writer.Close()
+	if err != nil {
+		return "", err
+	}
+
+	// 将字节流转换为百分号编码
+	percentEncoded := url.QueryEscape(buf.String())
+
+	return percentEncoded, nil
+}
+
+// AnalysisNoneMergeTablePars 解析无合并单元格的简单表格入参
+type AnalysisNoneMergeTablePars struct {
+	DocTable  *goquery.Selection
+	MarketCol struct {
+		HasCol   bool `description:"是否有市场列"`
+		ColIndex int  `description:"市场列"`
+	}
+	DateCol struct {
+		StartIndex  int       `description:"日期开始列"`
+		EndIndex    int       `description:"日期结束列"`
+		PublishTime time.Time `description:"报告发布时间"`
+		//PublishYear   int       `description:"报告发布年份"`
+		StrTimeFormat string   `description:"数据日期格式-需拼接日期列中的变量"`
+		TimeFormat    []string `description:"标准日期格式, 可能存在多种分别进行遍历"`
+		SplitLast     bool     `description:"是否分隔日期: 如1.24-1.28"`
+		SplitFlag     string   `description:"分隔日期分隔符: 如-"`
+	}
+	ValCol struct {
+		SplitHalfVal bool `description:"是否取折中值: 如8-10天, 9-12天"`
+	}
+}
+
+// TableRow 读取Table的行信息
+type TableRow struct {
+	Product  string
+	Market   string
+	DateData map[string]string
+	Unit     string
+}
+
+// analysisNoneMergeTable 解析无合并单元格的简单表格
+func analysisNoneMergeTable(params AnalysisNoneMergeTablePars) (items []TableRow) {
+	if params.DocTable != nil && params.DocTable.Length() <= 0 {
+		return
+	}
+
+	attemptDates := []string{"2006/1/2", "2006/01/02", "2006/01/2", "2006/1/02", "2006-1-2", "2006-01-02", "2006-01-2", "2006-1-02", "2006.01.02", "2006.1.2", "2006.1.02", "2006.01.2", "2006年01月02日", "2006年1月2日", "2006年1月02日", "2006年01月2日"}
+	colDate := make(map[int]string)
+	params.DocTable.Find("tbody").Children().Each(func(i int, s *goquery.Selection) {
+		cells := s.Find("td")
+
+		// 表头取出日期
+		if i == 0 {
+			cells.Each(func(ii int, ss *goquery.Selection) {
+				cellTxt := strings.TrimSpace(ss.Text())
+				//fmt.Println("cellTxt", cellTxt)
+				if ii >= params.DateCol.StartIndex && ii <= params.DateCol.EndIndex {
+					//strTime := fmt.Sprintf("%d.%s", publishYear, cellTxt)
+					//var strTimeFormat string
+					completeTime := cellTxt
+					// 是否需要拼接年份
+					if params.DateCol.StrTimeFormat != "" {
+						strDate := cellTxt
+						// 是否取分隔日期的后一个日期
+						if params.DateCol.SplitLast && params.DateCol.SplitFlag != "" {
+							dateArr := strings.Split(cellTxt, params.DateCol.SplitFlag)
+							if len(dateArr) > 1 {
+								strDate = dateArr[len(dateArr)-1]
+							}
+						}
+						completeTime = fmt.Sprintf(params.DateCol.StrTimeFormat, params.DateCol.PublishTime.Year(), strDate)
+					}
+					//fmt.Println("completeTime: ", completeTime)
+
+					// 遍历多种可能的日期格式
+					var colTime time.Time
+					for _, f := range params.DateCol.TimeFormat {
+						t, e := time.ParseInLocation(f, completeTime, time.Local)
+						if e != nil {
+							continue
+						}
+						colTime = t
+						break
+					}
+					// 统一判断一次, 入参的日期格式可能不全
+					if colTime.IsZero() {
+						utils.FileLog.Info(fmt.Sprintf("日期格式异常: cellTxt-%s; completeTime-%s", cellTxt, completeTime))
+						for _, f := range attemptDates {
+							t, e := time.ParseInLocation(f, completeTime, time.Local)
+							if e != nil {
+								continue
+							}
+							colTime = t
+							break
+						}
+					}
+					// 判断报告是否跨年
+					if colTime.AddDate(0, -6, 0).After(params.DateCol.PublishTime) {
+						utils.FileLog.Info(fmt.Sprintf("跨年判断: ColTime-%v; PublishTime-%v", colTime, params.DateCol.PublishTime))
+						colTime = colTime.AddDate(-1, 0, 0)
+					}
+					if !colTime.IsZero() {
+						colDate[ii] = colTime.Format(utils.FormatDate)
+					}
+					fmt.Println("日期:", colTime.Format(utils.FormatDate))
+				}
+			})
+		}
+
+		// 取指标
+		if i > 0 {
+			row := TableRow{
+				DateData: make(map[string]string),
+			}
+			cells.Each(func(ii int, ss *goquery.Selection) {
+				cellTxt := filterInvalidVal(ss.Text())
+				//fmt.Println("cellTxt", cellTxt)
+				if ii == 0 {
+					row.Product = cellTxt
+				}
+				if params.MarketCol.HasCol && ii == params.MarketCol.ColIndex {
+					row.Market = cellTxt
+				}
+				if ii >= params.DateCol.StartIndex && ii <= params.DateCol.EndIndex {
+					d, ok := colDate[ii]
+					if !ok {
+						return
+					}
+					// 是否取折中值
+					if params.ValCol.SplitHalfVal {
+						val, e := calculateDataHalfVal(cellTxt)
+						if e != nil {
+							fmt.Printf("calculateDataHalfVal err: %v\n", e)
+							return
+						}
+						cellTxt = val
+					}
+					if cellTxt != "" {
+						row.DateData[d] = cellTxt
+					}
+				}
+			})
+			//fmt.Println(row)
+			items = append(items, row)
+		}
+	})
+	return
+}
+
+// formatTableRow2ValidEdb 表格行转换为有效指标
+func formatTableRow2ValidEdb(rows []TableRow, edbMatch []DataRuleEdbMatch) (indexes []*HandleIndexData) {
+	indexes = make([]*HandleIndexData, 0)
+	for _, m := range edbMatch {
+		for _, v := range rows {
+			fmt.Printf("产品: %s, 市场: %s, 日期数据: %v, 单位: %s\n", v.Product, v.Market, v.DateData, v.Unit)
+			var productOk, marketOk, unitOk bool
+			if (m.Product == "" && v.Product == "") || (m.Product != "" && strings.Contains(v.Product, m.Product)) {
+				productOk = true
+			}
+			if (m.Market == "" && v.Market == "") || (m.Market != "" && strings.Contains(v.Market, m.Market)) {
+				marketOk = true
+			}
+			if (m.MatchUnit == "" && v.Unit == "") || (m.MatchUnit != "" && strings.Contains(v.Unit, m.MatchUnit)) {
+				unitOk = true
+			}
+			if productOk && marketOk && unitOk {
+				edb := new(HandleIndexData)
+				edb.IndexCode = m.IndexCode
+				edb.IndexName = m.IndexName
+				edb.ClassifyId = m.ClassifyId
+				edb.Frequency = m.Frequency
+				edb.Unit = m.Unit
+				edb.DateData = v.DateData
+				edb.TerminalCode = utils.TerminalCode
+				indexes = append(indexes, edb)
+			}
+		}
+	}
+	return
+}
+
+// listFiles 列出目录下所有文件名
+func listFiles(dirPath string) ([]string, error) {
+	var files []string
+
+	err := filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+		if !info.IsDir() {
+			files = append(files, info.Name())
+		}
+		return nil
+	})
+
+	if err != nil {
+		return nil, err
+	}
+	return files, nil
+}
+
+// filterInvalidVal 过滤无效值
+func filterInvalidVal(cellTxt string) string {
+	cellTxt = strings.TrimSpace(cellTxt)
+	if cellTxt == "休市" || cellTxt == "/" || cellTxt == "-" || cellTxt == "—" {
+		return ""
+	}
+	return cellTxt
+}
+
+// formatIntervalData 格式化区间值
+func formatIntervalData(cellTxt, flag string) string {
+	cellTxt = filterInvalidVal(cellTxt)
+
+	if flag == "" {
+		flag = "-"
+	}
+	matches := strings.Split(cellTxt, flag)
+	if len(matches) < 2 {
+		return cellTxt
+	}
+	if len(matches) != 2 {
+		return ""
+	}
+
+	// 转换不了直接返回空值
+	a, e := strconv.ParseFloat(matches[0], 64)
+	if e != nil {
+		return ""
+	}
+	b, e := strconv.ParseFloat(matches[1], 64)
+	if e != nil {
+		return ""
+	}
+	average := (a + b) / 2
+
+	return fmt.Sprint(average)
+}

+ 1333 - 0
services/base_from_ccf/edb.go

@@ -0,0 +1,1333 @@
+package base_from_ccf
+
+import (
+	"context"
+	"encoding/json"
+	"eta/eta_data_analysis/models"
+	"eta/eta_data_analysis/utils"
+	"fmt"
+	"github.com/PuerkitoBio/goquery"
+	"io/ioutil"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// TaskAnalysisHandlers 解析表格的函数
+var TaskAnalysisHandlers = map[string]func(htm []byte, fetchRule *DataRule) (indexes []*HandleIndexData, err error){
+	"原油石化早报": AnalysisOilReportEdb,
+	"PTA周报":  AnalysisPTAWeekEdb,
+	"MEG周报":  AnalysisMEGWeekEdb,
+	"长丝周报":   AnalysisChangSiWeekEdb,
+	"短纤周报":   AnalysisDuanXianWeekEdb,
+	"瓶片周报":   AnalysisPingPianWeekEdb,
+	"切片周报":   AnalysisQiePianWeekEdb,
+	"PX周报":   AnalysisPXWeekEdb,
+}
+
+// HandleIndexData 指标数据
+type HandleIndexData struct {
+	IndexName    string            `description:"指标名称"`
+	IndexCode    string            `description:"指标编码"`
+	ClassifyId   int               `description:"分类ID"`
+	Unit         string            `description:"单位"`
+	Sort         int               `description:"排序"`
+	Frequency    string            `description:"频度"`
+	TerminalCode string            `description:"终端编码"`
+	DateData     map[string]string `description:"日期数据"`
+}
+
+// TaskOilDailyEdb 获取原油石化早报指标
+func TaskOilDailyEdb(context.Context) (err error) {
+	defer func() {
+		if err != nil {
+			tips := fmt.Sprintf("TaskOilEdbDaily ErrMsg: %s", err.Error())
+			utils.FileLog.Info(tips)
+			fmt.Println(tips)
+		}
+	}()
+
+	nameKey := "原油石化早报"
+	fetchRule, e := loadDataRule(nameKey)
+	if e != nil {
+		err = fmt.Errorf("loadDataRule, err: %v", e)
+		return
+	}
+
+	// 解析前N篇报告
+	readLimit := 7
+	filePaths, e := savePageHtml(nameKey, fetchRule.PageDir, false, readLimit)
+	if e != nil {
+		err = fmt.Errorf("savePageHtml, err: %v", e)
+		return
+	}
+
+	readCount := 0
+	for _, v := range filePaths {
+		readCount += 1
+		if readCount > readLimit {
+			return
+		}
+
+		htm, e := ioutil.ReadFile(v)
+		if e != nil {
+			fmt.Printf("file: %s, ReadFile err: %v\n", v, e)
+			utils.FileLog.Info(fmt.Sprintf("file: %s, ReadFile err: %v", v, e))
+			continue
+		}
+
+		handler, ok := TaskAnalysisHandlers[nameKey]
+		if !ok {
+			utils.FileLog.Info(fmt.Sprintf("%s无解析函数\n", nameKey))
+			continue
+		}
+		indexes, e := handler(htm, fetchRule)
+		if e != nil {
+			fmt.Printf("file: %s, AnalysisOilReportEdb err: %v\n", v, e)
+			utils.FileLog.Info(fmt.Sprintf("file: %s, AnalysisOilReportEdb err: %v", v, e))
+			continue
+		}
+
+		// 写入数据库
+		params := make(map[string]interface{})
+		params["List"] = indexes
+		params["TerminalCode"] = utils.TerminalCode
+		result, e := postEdbLib(params, utils.LIB_ROUTE_CCF_EDB_HANDLE)
+		if e != nil {
+			b, _ := json.Marshal(params)
+			fmt.Printf("file: %s, postEdbLib err: %v, params: %s\n", v, e, string(b))
+			utils.FileLog.Info(fmt.Sprintf("file: %s, postEdbLib err: %v, params: %s", v, e, string(b)))
+			continue
+		}
+		resp := new(models.BaseEdbLibResponse)
+		if e = json.Unmarshal(result, &resp); e != nil {
+			fmt.Printf("file: %s, json.Unmarshal err: %v\n", v, e)
+			utils.FileLog.Info(fmt.Sprintf("file: %s, json.Unmarshal err: %v", v, e))
+			continue
+		}
+		if resp.Ret != 200 {
+			fmt.Printf("file: %s, Msg: %s, ErrMsg: %s\n", v, resp.Msg, resp.ErrMsg)
+			utils.FileLog.Info(fmt.Sprintf("file: %s, Msg: %s, ErrMsg: %s", v, resp.Msg, resp.ErrMsg))
+			continue
+		}
+	}
+	return
+}
+
+// TaskWeeklyEdb 获取周报指标
+func TaskWeeklyEdb(context.Context) (err error) {
+	defer func() {
+		if err != nil {
+			tips := fmt.Sprintf("TaskWeeklyEdb ErrMsg: %s", err.Error())
+			utils.FileLog.Info(tips)
+			fmt.Println(tips)
+		}
+	}()
+
+	taskNames := []string{"PTA周报", "MEG周报", "长丝周报", "短纤周报", "瓶片周报", "切片周报", "PX周报"}
+	readLimit := 3
+	for _, nameKey := range taskNames {
+		fmt.Printf("开始获取: %s\n", nameKey)
+
+		fetchRule, e := loadDataRule(nameKey)
+		if e != nil {
+			utils.FileLog.Info(fmt.Sprintf("%s无解析规则, err: %v\n", nameKey, e))
+			continue
+		}
+		handler, ok := TaskAnalysisHandlers[nameKey]
+		if !ok {
+			//fmt.Printf("%s无解析函数\n", nameKey)
+			utils.FileLog.Info(fmt.Sprintf("%s无解析函数\n", nameKey))
+			continue
+		}
+
+		// 解析前N篇报告
+		files, e := savePageHtml(nameKey, fetchRule.PageDir, false, readLimit)
+		if e != nil {
+			//fmt.Printf("%s保存首页失败, err: %v\n", nameKey, e)
+			utils.FileLog.Info(fmt.Sprintf("%s保存首页失败, err: %v\n", nameKey, e))
+			continue
+		}
+		readCount := 0
+		for _, v := range files {
+			readCount += 1
+			if readCount > readLimit {
+				break
+			}
+			htm, e := ioutil.ReadFile(v)
+			if e != nil {
+				//fmt.Printf("file: %s, ReadFile err: %v\n", v, e)
+				utils.FileLog.Info(fmt.Sprintf("file: %s, ReadFile err: %v", v, e))
+				continue
+			}
+
+			indexes, e := handler(htm, fetchRule)
+			if e != nil {
+				//fmt.Printf("file: %s, AnalysisOilReportEdb err: %v\n", v, e)
+				utils.FileLog.Info(fmt.Sprintf("file: %s, AnalysisOilReportEdb err: %v", v, e))
+				continue
+			}
+
+			// 写入数据库
+			params := make(map[string]interface{})
+			params["List"] = indexes
+			params["TerminalCode"] = utils.TerminalCode
+			result, e := postEdbLib(params, utils.LIB_ROUTE_CCF_EDB_HANDLE)
+			if e != nil {
+				b, _ := json.Marshal(params)
+				//fmt.Printf("file: %s, postEdbLib err: %v, params: %s\n", v, e, string(b))
+				utils.FileLog.Info(fmt.Sprintf("file: %s, postEdbLib err: %v, params: %s", v, e, string(b)))
+				continue
+			}
+			resp := new(models.BaseEdbLibResponse)
+			if e = json.Unmarshal(result, &resp); e != nil {
+				//fmt.Printf("file: %s, json.Unmarshal err: %v\n", v, e)
+				utils.FileLog.Info(fmt.Sprintf("file: %s, json.Unmarshal err: %v", v, e))
+				continue
+			}
+			if resp.Ret != 200 {
+				//fmt.Printf("file: %s, Msg: %s, ErrMsg: %s\n", v, resp.Msg, resp.ErrMsg)
+				utils.FileLog.Info(fmt.Sprintf("file: %s, Msg: %s, ErrMsg: %s", v, resp.Msg, resp.ErrMsg))
+				continue
+			}
+		}
+		fmt.Printf("结束获取: %s\n", nameKey)
+	}
+	return
+}
+
+// AnalysisOilReportEdb 解析原油石化早报中的指标数据
+func AnalysisOilReportEdb(htm []byte, fetchRule *DataRule) (indexes []*HandleIndexData, err error) {
+	if len(htm) == 0 || fetchRule == nil {
+		utils.FileLog.Info("htm empty")
+		return
+	}
+	doc, e := goquery.NewDocumentFromReader(strings.NewReader(string(htm)))
+	if e != nil {
+		err = fmt.Errorf("NewDocumentFromReader err: %v", e)
+		return
+	}
+
+	// 找到表格
+	keyElement := doc.Find("#newscontent")
+	tableBody := keyElement.ChildrenFiltered("table").First().ChildrenFiltered("tbody")
+	if tableBody.Length() <= 0 {
+		err = fmt.Errorf("表格未找到")
+		return
+	}
+	colDates := make(map[int]string)
+	colLen := tableBody.Children().First().Find("td").Length()
+	attemptDates := []string{"2006/1/2", "2006/01/02", "2006-01-02", "2006-1-2", "2006.01.02", "2006.1.2"}
+
+	var rows []TableRow
+	var mergeBegin, mergeRows int
+	var mergeProduct string
+	tableBody.Children().Each(func(i int, s *goquery.Selection) {
+		cells := s.Find("td")
+
+		// 从表头取出日期列
+		// 格式: [产品|市场|日期列(列数不定)|涨跌|单位]
+		if i == 0 {
+			cells.Each(func(ii int, ss *goquery.Selection) {
+				cellTxt := strings.TrimSpace(ss.Text())
+
+				if ii > 1 && ii < colLen-2 {
+					var d time.Time
+					// 尝试解析日期
+					for _, a := range attemptDates {
+						t, e := time.ParseInLocation(a, cellTxt, time.Local)
+						if e == nil {
+							d = t
+							break
+						}
+					}
+					//fmt.Println("colDate: ", d)
+					if !d.IsZero() {
+						colDates[ii] = d.Format(utils.FormatDate)
+					}
+				}
+			})
+		}
+
+		// 取指标
+		if i > 0 {
+			row := TableRow{
+				DateData: make(map[string]string),
+			}
+			mergedRow := false // 是否为被合并行
+
+			cellsLen := cells.Length()
+			cells.Each(func(ii int, cell *goquery.Selection) {
+				cellData := filterInvalidVal(cell.Text())
+				if cellData == "" {
+					return
+				}
+
+				switch ii {
+				case 0:
+					// 被合并行为市场列, 其余为产品列
+					hasMerge, _ := cell.Attr("rowspan")
+					if hasMerge != "" {
+						// 开始合并行
+						mergeRows, _ = strconv.Atoi(hasMerge)
+						mergeBegin = i
+						row.Product = cellData
+						mergeProduct = row.Product
+					} else {
+						// 被合并行的后一行, 重置合并计数
+						if i >= (mergeBegin + mergeRows) {
+							mergeBegin = 0
+							mergeRows = 0
+						}
+						// 被合并行, 第一列为市场
+						if mergeBegin > 0 && mergeRows > 0 && i < (mergeBegin+mergeRows) {
+							row.Product = mergeProduct
+							row.Market = cellData
+							mergedRow = true
+						}
+						if mergeBegin == 0 && mergeRows == 0 {
+							row.Product = cellData
+						}
+					}
+				case 1:
+					// 被合并行为日期列, 其余为市场列
+					if mergedRow {
+						d, ok := colDates[ii+1]
+						if ok {
+							row.DateData[d] = formatIntervalData(cellData, "")
+						}
+					} else {
+						row.Market = cellData
+					}
+				case cellsLen - 2:
+					// 忽略涨跌列
+				case cellsLen - 1:
+					row.Unit = cellData
+				default:
+					// 日期列
+					if mergedRow {
+						d, ok := colDates[ii+1]
+						if ok {
+							row.DateData[d] = formatIntervalData(cellData, "")
+						}
+					} else {
+						d, ok := colDates[ii]
+						if ok {
+							row.DateData[d] = formatIntervalData(cellData, "")
+						}
+					}
+				}
+			})
+			rows = append(rows, row)
+		}
+	})
+
+	// 只取需要的指标
+	indexes = formatTableRow2ValidEdb(rows, fetchRule.EdbMatch)
+	return
+}
+
+// AnalysisPTAWeekEdb 解析PTA周报中的指标数据
+func AnalysisPTAWeekEdb(htm []byte, fetchRule *DataRule) (indexes []*HandleIndexData, err error) {
+	if len(htm) == 0 || fetchRule == nil {
+		utils.FileLog.Info("htm empty")
+		return
+	}
+	doc, e := goquery.NewDocumentFromReader(strings.NewReader(string(htm)))
+	if e != nil {
+		err = fmt.Errorf("NewDocumentFromReader err: %v", e)
+		return
+	}
+	burdenTitle, ptaTitle := "负荷", "PTA库存"
+
+	// 从收藏按钮往上找table, 取出报告发布日期
+	collectEle := doc.Find("#savenews")
+	publishTimeTab := collectEle.ParentsFiltered("table").First()
+	publishTxt := publishTimeTab.Find("td:first-child").Text()
+	publishTime, e := extractReportPublishTime(publishTxt)
+	if e != nil {
+		err = fmt.Errorf("extractReportPublishTime err: %v", e)
+		return
+	}
+	//publishYear := publishTime.Year()
+	//fmt.Println(publishTime)
+	//fmt.Println("年份", publishYear)
+
+	// 遍历h2, 找出负荷和PTA库存下第一个table
+	burdenTable, ptaTable := new(goquery.Selection), new(goquery.Selection)
+	h2Selections := doc.Find("h2")
+	h2Selections.Each(func(i int, h2 *goquery.Selection) {
+		//fmt.Println(i, h2.Text())
+		if strings.Contains(h2.Text(), burdenTitle) {
+			burdenTable = h2.NextAllFiltered("table").First()
+		}
+		if strings.Contains(h2.Text(), ptaTitle) {
+			ptaTable = h2.NextAllFiltered("table").First()
+		}
+	})
+
+	// 负荷
+	//var rows []TableRow
+	//var burdenRows []TableRow
+	//var burdenDataTime string
+	//burdenTable.Find("tbody").Children().Each(func(i int, s *goquery.Selection) {
+	//	// 表头取出日期
+	//	cells := s.Find("td")
+	//	if i == 0 {
+	//		cells.Each(func(ii int, ss *goquery.Selection) {
+	//			cellTxt := strings.TrimSpace(ss.Text())
+	//			if ii == 2 {
+	//				strTime := fmt.Sprintf("%d年%s", publishYear, cellTxt)
+	//				t, e := time.ParseInLocation("2006年01月02日", strTime, time.Local)
+	//				if e != nil {
+	//					err = fmt.Errorf("解析PTA负荷数据日期失败, err: %v", e)
+	//					return
+	//				}
+	//				burdenDataTime = t.Format(utils.FormatDate)
+	//			}
+	//		})
+	//	}
+	//	// 取指标
+	//	if i > 0 {
+	//		row := TableRow{
+	//			DateData: make(map[string]string),
+	//		}
+	//		cells.Each(func(ii int, ss *goquery.Selection) {
+	//			cellTxt := strings.TrimSpace(ss.Text())
+	//			switch ii {
+	//			case 0:
+	//				row.Product = cellTxt
+	//			case 1:
+	//				row.Market = cellTxt
+	//			case 2:
+	//				row.DateData[burdenDataTime] = cellTxt
+	//			}
+	//		})
+	//		//row.Unit = burdenUnit
+	//		burdenRows = append(burdenRows, row)
+	//	}
+	//})
+	//rows = append(rows, burdenRows...)
+
+	var rows []TableRow
+	var analysisPars AnalysisNoneMergeTablePars
+	analysisPars.DocTable = burdenTable
+	analysisPars.MarketCol.HasCol = true
+	analysisPars.MarketCol.ColIndex = 1
+	analysisPars.DateCol.StartIndex = 2
+	analysisPars.DateCol.EndIndex = 3
+	analysisPars.DateCol.PublishTime = publishTime
+	//analysisPars.DateCol.PublishYear = publishYear
+	analysisPars.DateCol.StrTimeFormat = "%d年%s"
+	analysisPars.DateCol.TimeFormat = []string{"2006年01月02日", "2006年1月2日"}
+	burdenRows := analysisNoneMergeTable(analysisPars)
+	rows = append(rows, burdenRows...)
+
+	// PTA库存, 存在特殊格式
+	ptaRows := make(map[int]TableRow)
+	ptaTable.Find("tbody").Children().Each(func(i int, s *goquery.Selection) {
+		cells := s.Find("td")
+		cellLen := cells.Length()
+
+		// 判断tr下td的长度, 兼容处理
+		// td长度为2, 数据日期取发布日期
+		if cellLen == 2 {
+			if i == 0 {
+				cells.Each(func(ii int, ss *goquery.Selection) {
+					cellTxt := strings.TrimSpace(ss.Text())
+					row := TableRow{
+						Product:  cellTxt,
+						DateData: make(map[string]string),
+					}
+					ptaRows[ii] = row
+				})
+			}
+			if i > 0 {
+				cells.Each(func(ii int, ss *goquery.Selection) {
+					cellTxt := filterInvalidVal(ss.Text())
+					if cellTxt == "" {
+						return
+					}
+					val, e := calculateDataHalfVal(cellTxt)
+					if e != nil {
+						utils.FileLog.Info(fmt.Sprintf("PTA周报-calculateDataHalfVal: cellTxt-%s, err: %v", cellTxt, e))
+						return
+					}
+					ptaRows[ii].DateData[publishTime.Format(utils.FormatDate)] = val
+				})
+			}
+		}
+
+		// 大于2时, 内容第一列为日期
+		if cellLen > 2 {
+			if i == 0 {
+				cells.Each(func(ii int, ss *goquery.Selection) {
+					if ii == 0 {
+						return
+					}
+					cellTxt := strings.TrimSpace(ss.Text())
+					row := TableRow{
+						Product:  cellTxt,
+						DateData: make(map[string]string),
+					}
+					ptaRows[ii] = row
+				})
+			}
+			if i > 0 {
+				var dataTime string
+				cells.Each(func(ii int, ss *goquery.Selection) {
+					cellTxt := filterInvalidVal(ss.Text())
+					if cellTxt == "" {
+						return
+					}
+					if ii == 0 {
+						strTime := fmt.Sprintf("%d/%s", publishTime.Year(), cellTxt)
+						t, e := time.ParseInLocation("2006/1/2", strTime, time.Local)
+						if e != nil {
+							fmt.Printf("time parse err: %v", e)
+							return
+						}
+						// 判断报告是否跨年
+						if t.AddDate(0, -6, 0).After(publishTime) {
+							utils.FileLog.Info(fmt.Sprintf("跨年判断-2: ColTime-%v; PublishTime-%v", t, publishTime))
+							t = t.AddDate(-1, 0, 0)
+						}
+						dataTime = t.Format(utils.FormatDate)
+						return
+					}
+					val, e := calculateDataHalfVal(cellTxt)
+					if e != nil {
+						fmt.Printf("calculateDataHalfVal err: %v\n", e)
+						return
+					}
+					if dataTime != "" && val != "" {
+						ptaRows[ii].DateData[dataTime] = val
+					}
+				})
+			}
+		}
+	})
+	for _, v := range ptaRows {
+		rows = append(rows, v)
+	}
+
+	indexes = formatTableRow2ValidEdb(rows, fetchRule.EdbMatch)
+	return
+}
+
+// AnalysisMEGWeekEdb 解析MEG周报中的指标数据
+func AnalysisMEGWeekEdb(htm []byte, fetchRule *DataRule) (indexes []*HandleIndexData, err error) {
+	if len(htm) == 0 || fetchRule == nil {
+		utils.FileLog.Info("htm empty")
+		return
+	}
+	doc, e := goquery.NewDocumentFromReader(strings.NewReader(string(htm)))
+	if e != nil {
+		err = fmt.Errorf("NewDocumentFromReader err: %v", e)
+		return
+	}
+
+	burdenTitle, stockTitle := "CCF指数", "MEG华东港口库存情况"
+
+	// 从收藏按钮往上找table, 取出报告发布日期
+	collectEle := doc.Find("#savenews")
+	publishTimeTab := collectEle.ParentsFiltered("table").First()
+	publishTxt := publishTimeTab.Find("td:first-child").Text()
+	//fmt.Println("publishTxt: ", publishTxt)
+	publishTime, e := extractReportPublishTime(publishTxt)
+	if e != nil {
+		err = fmt.Errorf("extractReportPublishTime err: %v", e)
+		return
+	}
+	//publishYear := publishTime.Year()
+	//fmt.Println(publishTime)
+	//fmt.Println(publishYear)
+
+	// 遍历h2, 找出对应Table
+	burdenTable, stockTable := new(goquery.Selection), new(goquery.Selection)
+	//h2Selections := doc.Find("h2")
+	doc.Find("h2").Each(func(i int, h2 *goquery.Selection) {
+		//fmt.Println(i, h2.Text())
+		if strings.Contains(h2.Text(), burdenTitle) {
+			burdenTable = h2.NextAllFiltered("table").First()
+		}
+		if strings.Contains(h2.Text(), stockTitle) {
+			stockTable = h2.NextAllFiltered("table").First()
+		}
+	})
+
+	// 负荷-存在合并行
+	var rows []TableRow
+	//var burdenRows []TableRow
+	{
+		//var burdenDataTime string
+		var mergeBegin, mergeRows int
+		var mergeProduct string
+		burdenColDate := make(map[int]string) // 日期列key->日期
+		burdenTable.Find("tbody").Children().Each(func(i int, s *goquery.Selection) {
+			// 表头取出日期
+			cells := s.Find("td")
+			if i == 0 {
+				cells.Each(func(ii int, ss *goquery.Selection) {
+					cellTxt := strings.TrimSpace(ss.Text())
+					if cellTxt == "" {
+						return
+					}
+					if ii == 2 || ii == 3 {
+						//fmt.Println("日期列")
+						strTime := fmt.Sprintf("%d年%s", publishTime.Year(), cellTxt)
+						//fmt.Println("日期str", strTime)
+						t, e := time.ParseInLocation("2006年1月2日", strTime, time.Local)
+						if e != nil {
+							utils.FileLog.Info(fmt.Sprintf("MEG周报-日期解析: cellTxt-%s, err: %v", cellTxt, e))
+							//fmt.Println("e: ", e)
+							//err = fmt.Errorf("解析MEG负荷数据日期失败, err: %v", e)
+							return
+						}
+						// 判断报告是否跨年
+						if t.AddDate(0, -6, 0).After(publishTime) {
+							utils.FileLog.Info(fmt.Sprintf("跨年判断-MEG: ColTime-%v; PublishTime-%v", t, publishTime))
+							t = t.AddDate(-1, 0, 0)
+						}
+						if !t.IsZero() {
+							burdenColDate[ii] = t.Format(utils.FormatDate)
+						}
+						//fmt.Println("日期:", t.Format(utils.FormatDate))
+					}
+				})
+			}
+			// 取指标
+			if i > 0 {
+				row := TableRow{
+					DateData: make(map[string]string),
+				}
+				mergedRow := false // 是否为被合并行
+				cells.Each(func(ii int, ss *goquery.Selection) {
+					cellTxt := filterInvalidVal(ss.Text())
+					if cellTxt == "" {
+						return
+					}
+					switch ii {
+					case 0:
+						// 被合并行为市场列, 其余为产品列
+						hasMerge, _ := ss.Attr("rowspan")
+						if hasMerge != "" {
+							// 开始合并行
+							mergeRows, _ = strconv.Atoi(hasMerge)
+							mergeBegin = i
+							row.Product = cellTxt
+							mergeProduct = row.Product
+						} else {
+							// 被合并行的后一行, 重置合并计数
+							if i >= (mergeBegin + mergeRows) {
+								mergeBegin = 0
+								mergeRows = 0
+							}
+							// 被合并行第一列为产品
+							if mergeBegin > 0 && mergeRows > 0 && i < (mergeBegin+mergeRows) {
+								row.Product = mergeProduct
+								row.Market = cellTxt
+								mergedRow = true
+								//fmt.Println("被合并行: ", i, mergeBegin+mergeRows)
+							}
+							if mergeBegin == 0 && mergeRows == 0 {
+								row.Product = cellTxt
+							}
+						}
+					case 1:
+						// 被合并行为值列, 其余为市场列
+						if mergedRow {
+							d, ok := burdenColDate[ii+1]
+							if ok {
+								row.DateData[d] = cellTxt
+							}
+						} else {
+							row.Market = cellTxt
+						}
+					case 2:
+						if mergedRow {
+							d, ok := burdenColDate[ii+1]
+							if ok {
+								row.DateData[d] = cellTxt
+							}
+						} else {
+							d, ok := burdenColDate[ii]
+							if ok {
+								row.DateData[d] = cellTxt
+							}
+						}
+					case 3:
+						if !mergedRow {
+							d, ok := burdenColDate[ii]
+							if ok {
+								row.DateData[d] = cellTxt
+							}
+						}
+					}
+				})
+				rows = append(rows, row)
+			}
+		})
+	}
+
+	// 库存
+	//var stockRows []TableRow
+	//{
+	//	colDate := make(map[int]string) // 日期列key->日期
+	//	stockTable.Find("tbody").Children().Each(func(i int, s *goquery.Selection) {
+	//		cells := s.Find("td")
+	//
+	//		// 表头取出日期
+	//		if i == 0 {
+	//			cells.Each(func(ii int, ss *goquery.Selection) {
+	//				cellTxt := strings.TrimSpace(ss.Text())
+	//				if ii > 0 {
+	//					t, e := time.ParseInLocation("2006/1/2", cellTxt, time.Local)
+	//					if e != nil {
+	//						fmt.Println("e: ", e)
+	//						//err = fmt.Errorf("解析MEG负荷数据日期失败, err: %v", e)
+	//						return
+	//					}
+	//					colDate[ii] = t.Format(utils.FormatDate)
+	//					fmt.Println("日期:", t.Format(utils.FormatDate))
+	//				}
+	//			})
+	//		}
+	//
+	//		// 取指标
+	//		if i > 0 {
+	//			row := TableRow{
+	//				Product: stockTitle,
+	//				//Unit:     stockUnit,
+	//				DateData: make(map[string]string),
+	//			}
+	//			cells.Each(func(ii int, ss *goquery.Selection) {
+	//				cellTxt := strings.TrimSpace(ss.Text())
+	//				switch ii {
+	//				case 0:
+	//					row.Market = cellTxt
+	//				case 1, 2:
+	//					row.DateData[colDate[ii]] = cellTxt
+	//				}
+	//			})
+	//			fmt.Println(row)
+	//			stockRows = append(stockRows, row)
+	//		}
+	//	})
+	//}
+
+	var analysisPars AnalysisNoneMergeTablePars
+	analysisPars.DocTable = stockTable
+	analysisPars.DateCol.StartIndex = 1
+	analysisPars.DateCol.EndIndex = 2
+	analysisPars.DateCol.PublishTime = publishTime
+	//analysisPars.DateCol.PublishYear = publishYear
+	analysisPars.DateCol.StrTimeFormat = ""
+	analysisPars.DateCol.TimeFormat = []string{"2006/1/2"}
+	stockRows := analysisNoneMergeTable(analysisPars)
+	rows = append(rows, stockRows...)
+
+	indexes = formatTableRow2ValidEdb(rows, fetchRule.EdbMatch)
+	//fmt.Println(111)
+	return
+}
+
+// AnalysisChangSiWeekEdb 解析长丝周报中的指标数据
+func AnalysisChangSiWeekEdb(htm []byte, fetchRule *DataRule) (indexes []*HandleIndexData, err error) {
+	if len(htm) == 0 || fetchRule == nil {
+		utils.FileLog.Info("htm empty")
+		return
+	}
+	doc, e := goquery.NewDocumentFromReader(strings.NewReader(string(htm)))
+	if e != nil {
+		err = fmt.Errorf("NewDocumentFromReader err: %v", e)
+		return
+	}
+
+	burdenTitle, stockTitle, observeTitle := "负荷指数", "库存指数", "下游观察"
+
+	// 从收藏按钮往上找table, 取出报告发布日期
+	collectEle := doc.Find("#savenews")
+	publishTimeTab := collectEle.ParentsFiltered("table").First()
+	publishTxt := publishTimeTab.Find("td:first-child").Text()
+	//fmt.Println("publishTxt: ", publishTxt)
+	publishTime, e := extractReportPublishTime(publishTxt)
+	if e != nil {
+		err = fmt.Errorf("extractReportPublishTime err: %v", e)
+		return
+	}
+	//publishYear := publishTime.Year()
+	//fmt.Println(publishTime)
+	//fmt.Println(publishYear)
+
+	// 遍历h2, 找出对应Table
+	burdenTable, stockTable, observeTable := new(goquery.Selection), new(goquery.Selection), new(goquery.Selection)
+	//h2Selections := doc.Find("h2")
+	doc.Find("h2").Each(func(i int, h2 *goquery.Selection) {
+		//fmt.Println(i, h2.Text())
+		if strings.Contains(h2.Text(), burdenTitle) {
+			burdenTable = h2.NextAllFiltered("table").First()
+		}
+		if strings.Contains(h2.Text(), stockTitle) {
+			stockTable = h2.NextAllFiltered("table").First()
+		}
+		if strings.Contains(h2.Text(), observeTitle) {
+			observeTable = h2.NextAllFiltered("table").First()
+		}
+	})
+
+	// 负荷/下游观察解析
+	//noneMergeAnalysis := func(docTable *goquery.Selection, unit string) (items []TableRow) {
+	//	colDate := make(map[int]string)
+	//
+	//	docTable.Find("tbody").Children().Each(func(i int, s *goquery.Selection) {
+	//		cells := s.Find("td")
+	//
+	//		// 表头取出日期
+	//		if i == 0 {
+	//			cells.Each(func(ii int, ss *goquery.Selection) {
+	//				cellTxt := strings.TrimSpace(ss.Text())
+	//				fmt.Println("cellTxt", cellTxt)
+	//				if ii >= 1 && ii <= 3 {
+	//					strTime := fmt.Sprintf("%d.%s", publishYear, cellTxt)
+	//					t, e := time.ParseInLocation("2006.01.02", strTime, time.Local)
+	//					if e != nil {
+	//						fmt.Println("e: ", e)
+	//						//err = fmt.Errorf("解析MEG负荷数据日期失败, err: %v", e)
+	//						return
+	//					}
+	//					colDate[ii] = t.Format(utils.FormatDate)
+	//					fmt.Println("日期:", t.Format(utils.FormatDate))
+	//				}
+	//			})
+	//		}
+	//
+	//		// 取指标
+	//		if i > 0 {
+	//			row := TableRow{
+	//				//Product:  stockTitle,
+	//				Unit:     unit,
+	//				DateData: make(map[string]string),
+	//			}
+	//			cells.Each(func(ii int, ss *goquery.Selection) {
+	//				cellTxt := strings.TrimSpace(ss.Text())
+	//				fmt.Println("cellTxt", cellTxt)
+	//				switch ii {
+	//				case 0:
+	//					row.Product = cellTxt
+	//				case 1, 2, 3:
+	//					row.DateData[colDate[ii]] = cellTxt
+	//				}
+	//			})
+	//			//fmt.Println(row)
+	//			items = append(items, row)
+	//		}
+	//	})
+	//	return
+	//}
+
+	// 库存解析-存在合并行
+	mergeAnalysis := func(docTable *goquery.Selection) (items []TableRow) {
+		var mergeBegin, mergeRows int
+		var mergeProduct string
+		colDate := make(map[int]string) // 日期列key->日期
+		attemptDates := []string{"2006.01.02", "2006.1.02", "2006.01.2"}
+		docTable.Find("tbody").Children().Each(func(i int, s *goquery.Selection) {
+			// 表头取出日期
+			cells := s.Find("td")
+			if i == 0 {
+				cells.Each(func(ii int, ss *goquery.Selection) {
+					cellTxt := strings.TrimSpace(ss.Text())
+					fmt.Println("1-cellTxt", cellTxt)
+					if ii >= 2 && ii <= 4 {
+						//fmt.Println("日期列")
+						strTime := fmt.Sprintf("%d.%s", publishTime.Year(), cellTxt)
+						//fmt.Println("日期str", strTime)
+						//t, e := time.ParseInLocation("2006.01.02", strTime, time.Local)
+						//if e != nil {
+						//	utils.FileLog.Info(fmt.Sprintf("长丝周报-日期解析: cellTxt-%s, err: %v", cellTxt, e))
+						//	//fmt.Println("time e: ", e)
+						//	//err = fmt.Errorf("解析MEG负荷数据日期失败, err: %v", e)
+						//	return
+						//}
+						var colTime time.Time
+						for _, f := range attemptDates {
+							t, e := time.ParseInLocation(f, strTime, time.Local)
+							if e != nil {
+								continue
+							}
+							colTime = t
+							break
+						}
+
+						// 判断报告是否跨年
+						if colTime.AddDate(0, -6, 0).After(publishTime) {
+							utils.FileLog.Info(fmt.Sprintf("跨年判断-长丝: ColTime-%v; PublishTime-%v", colTime, publishTime))
+							colTime = colTime.AddDate(-1, 0, 0)
+						}
+						if !colTime.IsZero() {
+							colDate[ii] = colTime.Format(utils.FormatDate)
+						}
+						//fmt.Println("日期:", t.Format(utils.FormatDate))
+					}
+				})
+			}
+
+			// 取指标
+			if i > 0 {
+				row := TableRow{
+					DateData: make(map[string]string),
+				}
+				mergedRow := false // 是否为被合并行
+				cells.Each(func(ii int, ss *goquery.Selection) {
+					cellTxt := filterInvalidVal(ss.Text())
+					fmt.Println("2-cellTxt", cellTxt)
+					switch ii {
+					case 0:
+						// 被合并行为市场列, 其余为产品列
+						hasMerge, _ := ss.Attr("rowspan")
+						if hasMerge != "" {
+							// 开始合并行
+							mergeRows, _ = strconv.Atoi(hasMerge)
+							mergeBegin = i
+							row.Product = cellTxt
+							mergeProduct = row.Product
+						} else {
+							// 被合并行的后一行, 重置合并计数
+							if i >= (mergeBegin + mergeRows) {
+								mergeBegin = 0
+								mergeRows = 0
+							}
+							// 被合并行第一列为产品
+							if mergeBegin > 0 && mergeRows > 0 && i < (mergeBegin+mergeRows) {
+								row.Product = mergeProduct
+								row.Market = cellTxt
+								mergedRow = true
+								//fmt.Println("被合并行: ", i, mergeBegin+mergeRows)
+							}
+							if mergeBegin == 0 && mergeRows == 0 {
+								row.Product = cellTxt
+							}
+						}
+					case 1:
+						// 被合并行为值列, 其余为市场列
+						if mergedRow {
+							d, ok := colDate[ii+1]
+							if ok {
+								row.DateData[d] = cellTxt
+							}
+						} else {
+							row.Market = cellTxt
+						}
+					case 2, 3:
+						if mergedRow {
+							d, ok := colDate[ii+1]
+							if ok {
+								row.DateData[d] = cellTxt
+							}
+						} else {
+							d, ok := colDate[ii]
+							if ok {
+								row.DateData[d] = cellTxt
+							}
+						}
+					case 4:
+						if !mergedRow {
+							d, ok := colDate[ii]
+							if ok {
+								row.DateData[d] = cellTxt
+							}
+						}
+					}
+				})
+				items = append(items, row)
+			}
+		})
+		return
+	}
+
+	// 负荷
+	var rows []TableRow
+	fmt.Println("blen", burdenTable.Length())
+	if burdenTable.Length() > 0 {
+		//items := noneMergeAnalysis(burdenTable, burdenUnit)
+		//if len(items) > 0 {
+		//	rows = append(rows, items...)
+		//}
+		//strTime := fmt.Sprintf("%d.%s", publishYear, cellTxt)
+		//t, e := time.ParseInLocation("2006.01.02", strTime, time.Local)
+		var analysisPars AnalysisNoneMergeTablePars
+		analysisPars.DocTable = burdenTable
+		analysisPars.DateCol.StartIndex = 1
+		analysisPars.DateCol.EndIndex = 3
+		analysisPars.DateCol.PublishTime = publishTime
+		//analysisPars.DateCol.PublishYear = publishYear
+		analysisPars.DateCol.StrTimeFormat = "%d.%s"
+		analysisPars.DateCol.TimeFormat = []string{"2006.01.02"}
+		burdenRows := analysisNoneMergeTable(analysisPars)
+		rows = append(rows, burdenRows...)
+	}
+
+	// 下游观察
+	fmt.Println("olen", observeTable.Length())
+	if observeTable.Length() > 0 {
+		//items := noneMergeAnalysis(observeTable, observeUnit)
+		//if len(items) > 0 {
+		//	rows = append(rows, items...)
+		//}
+		var analysisPars AnalysisNoneMergeTablePars
+		analysisPars.DocTable = observeTable
+		analysisPars.DateCol.StartIndex = 1
+		analysisPars.DateCol.EndIndex = 3
+		analysisPars.DateCol.PublishTime = publishTime
+		//analysisPars.DateCol.PublishYear = publishYear
+		analysisPars.DateCol.StrTimeFormat = "%d.%s"
+		analysisPars.DateCol.TimeFormat = []string{"2006.01.02"}
+		observeRows := analysisNoneMergeTable(analysisPars)
+		rows = append(rows, observeRows...)
+	}
+
+	// 下游观察
+	fmt.Println("slen", stockTable.Length())
+	if stockTable.Length() > 0 {
+		//fmt.Println(stockUnit)
+		items := mergeAnalysis(stockTable)
+		if len(items) > 0 {
+			rows = append(rows, items...)
+		}
+	}
+	fmt.Println(rows)
+
+	indexes = formatTableRow2ValidEdb(rows, fetchRule.EdbMatch)
+	return
+}
+
+// AnalysisDuanXianWeekEdb 解析短纤周报中的指标数据
+func AnalysisDuanXianWeekEdb(htm []byte, fetchRule *DataRule) (indexes []*HandleIndexData, err error) {
+	if len(htm) == 0 || fetchRule == nil {
+		utils.FileLog.Info("htm empty")
+		return
+	}
+	doc, e := goquery.NewDocumentFromReader(strings.NewReader(string(htm)))
+	if e != nil {
+		err = fmt.Errorf("NewDocumentFromReader err: %v", e)
+		return
+	}
+
+	mainTitle := "主要运行指数"
+
+	// 从收藏按钮往上找table, 取出报告发布日期
+	collectEle := doc.Find("#savenews")
+	publishTimeTab := collectEle.ParentsFiltered("table").First()
+	publishTxt := publishTimeTab.Find("td:first-child").Text()
+	//fmt.Println("publishTxt: ", publishTxt)
+	publishTime, e := extractReportPublishTime(publishTxt)
+	if e != nil {
+		err = fmt.Errorf("extractReportPublishTime err: %v", e)
+		return
+	}
+	//publishYear := publishTime.Year()
+	//fmt.Println(publishTime)
+	//fmt.Println(publishYear)
+
+	// 查找包含文本的<p>元素
+	mainElement := doc.Find(fmt.Sprintf("p:contains('%s')", mainTitle))
+	if mainElement.Length() <= 0 {
+		err = fmt.Errorf("未找到p标签, keyword: %s", mainTitle)
+		return
+	}
+	table := mainElement.NextAllFiltered("table").First()
+	if table.Length() <= 0 {
+		err = fmt.Errorf("未找到p标签后的table, keyword: %s", mainTitle)
+		return
+	}
+
+	var analysisPars AnalysisNoneMergeTablePars
+	analysisPars.DocTable = table
+	analysisPars.DateCol.StartIndex = 1
+	analysisPars.DateCol.EndIndex = 2
+	analysisPars.DateCol.PublishTime = publishTime
+	//analysisPars.DateCol.PublishYear = publishYear
+	analysisPars.DateCol.StrTimeFormat = "%d年%s"
+	analysisPars.DateCol.TimeFormat = []string{"2006年1月2日"}
+	rows := analysisNoneMergeTable(analysisPars)
+	fmt.Println(rows)
+
+	indexes = formatTableRow2ValidEdb(rows, fetchRule.EdbMatch)
+	return
+}
+
+// AnalysisPingPianWeekEdb 解析瓶片周报中的指标数据
+func AnalysisPingPianWeekEdb(htm []byte, fetchRule *DataRule) (indexes []*HandleIndexData, err error) {
+	if len(htm) == 0 || fetchRule == nil {
+		utils.FileLog.Info("htm empty")
+		return
+	}
+	doc, e := goquery.NewDocumentFromReader(strings.NewReader(string(htm)))
+	if e != nil {
+		err = fmt.Errorf("NewDocumentFromReader err: %v", e)
+		return
+	}
+
+	mainTitle := "周均负荷指数"
+
+	// 从收藏按钮往上找table, 取出报告发布日期
+	collectEle := doc.Find("#savenews")
+	publishTimeTab := collectEle.ParentsFiltered("table").First()
+	publishTxt := publishTimeTab.Find("td:first-child").Text()
+	//fmt.Println("publishTxt: ", publishTxt)
+	publishTime, e := extractReportPublishTime(publishTxt)
+	if e != nil {
+		err = fmt.Errorf("extractReportPublishTime err: %v", e)
+		return
+	}
+	//publishYear := publishTime.Year()
+	//fmt.Println(publishTime)
+	//fmt.Println(publishYear)
+
+	// 查找包含文本的<p>元素
+	mainElement := doc.Find(fmt.Sprintf("h2:contains('%s')", mainTitle))
+	if mainElement.Length() <= 0 {
+		err = fmt.Errorf("未找到p标签, keyword: %s", mainTitle)
+		return
+	}
+	table := mainElement.NextAllFiltered("table").First()
+	if table.Length() <= 0 {
+		err = fmt.Errorf("未找到p标签后的table, keyword: %s", mainTitle)
+		return
+	}
+
+	var analysisPars AnalysisNoneMergeTablePars
+	analysisPars.DocTable = table
+	analysisPars.DateCol.StartIndex = 1
+	analysisPars.DateCol.EndIndex = 3
+	analysisPars.DateCol.PublishTime = publishTime
+	//analysisPars.DateCol.PublishYear = publishYear
+	analysisPars.DateCol.StrTimeFormat = "%d.%s"
+	analysisPars.DateCol.TimeFormat = []string{"2006.1.2"}
+	analysisPars.DateCol.SplitLast = true
+	analysisPars.DateCol.SplitFlag = "-"
+	rows := analysisNoneMergeTable(analysisPars)
+	fmt.Println(rows)
+	indexes = formatTableRow2ValidEdb(rows, fetchRule.EdbMatch)
+	return
+}
+
+// AnalysisQiePianWeekEdb 解析切片周报中的指标数据
+func AnalysisQiePianWeekEdb(htm []byte, fetchRule *DataRule) (indexes []*HandleIndexData, err error) {
+	if len(htm) == 0 || fetchRule == nil {
+		utils.FileLog.Info("htm empty")
+		return
+	}
+	doc, e := goquery.NewDocumentFromReader(strings.NewReader(string(htm)))
+	if e != nil {
+		err = fmt.Errorf("NewDocumentFromReader err: %v", e)
+		return
+	}
+
+	mainTitle := "切片纺方面"
+
+	// 从收藏按钮往上找table, 取出报告发布日期
+	collectEle := doc.Find("#savenews")
+	publishTimeTab := collectEle.ParentsFiltered("table").First()
+	publishTxt := publishTimeTab.Find("td:first-child").Text()
+	//fmt.Println("publishTxt: ", publishTxt)
+	publishTime, e := extractReportPublishTime(publishTxt)
+	if e != nil {
+		err = fmt.Errorf("extractReportPublishTime err: %v", e)
+		return
+	}
+	//publishYear := publishTime.Year()
+	//fmt.Println(publishTime)
+	//fmt.Println(publishYear)
+
+	// 查找包含关键词的标签
+	mainElement := doc.Find(fmt.Sprintf("h2:contains('%s')", mainTitle))
+	if mainElement.Length() <= 0 {
+		err = fmt.Errorf("未找到关键词标签, keyword: %s", mainTitle)
+		return
+	}
+	table := mainElement.NextAllFiltered("table").First()
+	if table.Length() <= 0 {
+		err = fmt.Errorf("未找到p标签后的table, keyword: %s", mainTitle)
+		return
+	}
+
+	var analysisPars AnalysisNoneMergeTablePars
+	analysisPars.DocTable = table
+	analysisPars.DateCol.StartIndex = 1
+	analysisPars.DateCol.EndIndex = 3
+	analysisPars.DateCol.PublishTime = publishTime
+	//analysisPars.DateCol.PublishYear = publishYear
+	analysisPars.DateCol.StrTimeFormat = ""
+	analysisPars.DateCol.TimeFormat = []string{"2006-1-2", "2006/1/2"}
+	analysisPars.ValCol.SplitHalfVal = true
+	rows := analysisNoneMergeTable(analysisPars)
+	fmt.Println(rows)
+	indexes = formatTableRow2ValidEdb(rows, fetchRule.EdbMatch)
+	return
+}
+
+// AnalysisPXWeekEdb 解析PX周报中的指标数据
+func AnalysisPXWeekEdb(htm []byte, fetchRule *DataRule) (indexes []*HandleIndexData, err error) {
+	if len(htm) == 0 || fetchRule == nil {
+		utils.FileLog.Info("htm empty")
+		return
+	}
+	doc, e := goquery.NewDocumentFromReader(strings.NewReader(string(htm)))
+	if e != nil {
+		err = fmt.Errorf("NewDocumentFromReader err: %v", e)
+		return
+	}
+
+	mainTitle := "负荷指数"
+
+	// 从收藏按钮往上找table, 取出报告发布日期
+	collectEle := doc.Find("#savenews")
+	publishTimeTab := collectEle.ParentsFiltered("table").First()
+	publishTxt := publishTimeTab.Find("td:first-child").Text()
+	//fmt.Println("publishTxt: ", publishTxt)
+	publishTime, e := extractReportPublishTime(publishTxt)
+	if e != nil {
+		err = fmt.Errorf("extractReportPublishTime err: %v", e)
+		return
+	}
+	//publishYear := publishTime.Year()
+	//fmt.Println(publishTime)
+	//fmt.Println(publishYear)
+
+	// 查找包含关键词的标签
+	mainElement := doc.Find(fmt.Sprintf("h2:contains('%s')", mainTitle))
+	if mainElement.Length() <= 0 {
+		err = fmt.Errorf("未找到关键词标签, keyword: %s", mainTitle)
+		return
+	}
+	table := mainElement.NextAllFiltered("table").First()
+	if table.Length() <= 0 {
+		err = fmt.Errorf("未找到p标签后的table, keyword: %s", mainTitle)
+		return
+	}
+
+	var analysisPars AnalysisNoneMergeTablePars
+	analysisPars.DocTable = table
+	analysisPars.DateCol.StartIndex = 1
+	analysisPars.DateCol.EndIndex = 3
+	analysisPars.DateCol.PublishTime = publishTime
+	//analysisPars.DateCol.PublishYear = publishYear
+	analysisPars.DateCol.StrTimeFormat = "%d年%s"
+	analysisPars.DateCol.TimeFormat = []string{"2006年1月2日"}
+	rows := analysisNoneMergeTable(analysisPars)
+	fmt.Println(rows)
+	indexes = formatTableRow2ValidEdb(rows, fetchRule.EdbMatch)
+	return
+}
+
+// FetchHistoryFiles 获取历史文件
+func FetchHistoryFiles(context.Context) {
+	var err error
+	defer func() {
+		if err != nil {
+			tips := fmt.Sprintf("FetchEdbHistoryFiles ErrMsg: %s", err.Error())
+			utils.FileLog.Info(tips)
+			fmt.Println(tips)
+		}
+	}()
+
+	taskNames := []string{"原油石化早报", "PTA周报", "MEG周报", "长丝周报", "短纤周报", "瓶片周报", "切片周报", "PX周报", "PTA装置", "MEG装置", "PX装置"}
+	//taskNames := []string{"原油石化早报"}
+	for _, nameKey := range taskNames {
+		fmt.Println("开始获取: ", nameKey)
+		fetchRule, e := loadDataRule(nameKey)
+		if e != nil {
+			err = fmt.Errorf("loadDataRule, err: %v", e)
+			return
+		}
+
+		_, e = savePageHtml(nameKey, fetchRule.PageDir, true, 0)
+		if e != nil {
+			err = fmt.Errorf("savePageHtml, err: %v", e)
+			return
+		}
+		fmt.Println("结束获取: ", nameKey)
+	}
+	return
+}
+
+// ReadEdbHistoryFiles 读取历史文件
+func ReadEdbHistoryFiles(context.Context) {
+	var err error
+	defer func() {
+		if err != nil {
+			tips := fmt.Sprintf("ReadEdbHistoryFiles ErrMsg: %s", err.Error())
+			utils.FileLog.Info(tips)
+			fmt.Println(tips)
+		}
+	}()
+
+	taskNames := []string{"原油石化早报", "PTA周报", "MEG周报", "长丝周报", "短纤周报", "瓶片周报", "切片周报", "PX周报"}
+	//taskNames := []string{"原油石化早报", "PTA周报", "MEG周报", "长丝周报", "短纤周报", "瓶片周报", "切片周报", "PX周报"}
+	for _, nameKey := range taskNames {
+		fetchRule, e := loadDataRule(nameKey)
+		if e != nil {
+			utils.FileLog.Info(fmt.Sprintf("%s无解析规则, err: %v\n", nameKey, e))
+			continue
+		}
+
+		filePaths, e := listFiles(fetchRule.PageDir)
+		if e != nil {
+			utils.FileLog.Info(fmt.Sprintf("%s读取文件目录失败, err: %v\n", nameKey, e))
+			continue
+		}
+
+		for _, v := range filePaths {
+			//if k > 0 {
+			//	break
+			//}
+			v = fmt.Sprintf("%s/%s", fetchRule.PageDir, v)
+			fmt.Printf("开始解析: %s", v)
+			//htm, e := ioutil.ReadFile("static/ccf/oil_daily/28-20240604-原油石化早报(6.7).html")
+			htm, e := ioutil.ReadFile(v)
+			if e != nil {
+				fmt.Printf("file: %s, ReadFile err: %v\n", v, e)
+				utils.FileLog.Info(fmt.Sprintf("file: %s, ReadFile err: %v", v, e))
+				continue
+			}
+
+			handler, ok := TaskAnalysisHandlers[nameKey]
+			if !ok {
+				utils.FileLog.Info(fmt.Sprintf("%s无解析函数\n", nameKey))
+				continue
+			}
+			indexes, e := handler(htm, fetchRule)
+			if e != nil {
+				fmt.Printf("file: %s, handler err: %v\n", v, e)
+				utils.FileLog.Info(fmt.Sprintf("file: %s, handler err: %v", v, e))
+				continue
+			}
+
+			// 写入数据库
+			params := make(map[string]interface{})
+			params["List"] = indexes
+			params["TerminalCode"] = utils.TerminalCode
+			result, e := postEdbLib(params, utils.LIB_ROUTE_CCF_EDB_HANDLE)
+			if e != nil {
+				b, _ := json.Marshal(params)
+				fmt.Printf("file: %s, postEdbLib err: %v, params: %s\n", v, e, string(b))
+				utils.FileLog.Info(fmt.Sprintf("file: %s, postEdbLib err: %v, params: %s", v, e, string(b)))
+				continue
+			}
+			resp := new(models.BaseEdbLibResponse)
+			if e = json.Unmarshal(result, &resp); e != nil {
+				fmt.Printf("file: %s, json.Unmarshal err: %v\n", v, e)
+				utils.FileLog.Info(fmt.Sprintf("file: %s, json.Unmarshal err: %v", v, e))
+				continue
+			}
+			if resp.Ret != 200 {
+				fmt.Printf("file: %s, Msg: %s, ErrMsg: %s\n", v, resp.Msg, resp.ErrMsg)
+				utils.FileLog.Info(fmt.Sprintf("file: %s, Msg: %s, ErrMsg: %s", v, resp.Msg, resp.ErrMsg))
+				continue
+			}
+		}
+	}
+	return
+}

+ 215 - 0
services/base_from_ccf/stock.go

@@ -0,0 +1,215 @@
+package base_from_ccf
+
+import (
+	"context"
+	"encoding/json"
+	"eta/eta_data_analysis/models"
+	"eta/eta_data_analysis/utils"
+	"fmt"
+	"github.com/PuerkitoBio/goquery"
+	"io/ioutil"
+	"strings"
+	"time"
+)
+
+// HandleTableData 表格数据
+type HandleTableData struct {
+	ClassifyId   int       `description:"分类ID"`
+	FromPage     string    `description:"表格来源"`
+	TableDate    time.Time `description:"表格日期"`
+	TableContent string    `description:"表格HTML"`
+}
+
+// TaskStockTable 获取装置表格
+func TaskStockTable(context.Context) (err error) {
+	defer func() {
+		if err != nil {
+			tips := fmt.Sprintf("TaskStockTable ErrMsg: %s", err.Error())
+			utils.FileLog.Info(tips)
+			fmt.Println(tips)
+		}
+	}()
+
+	taskNames := []string{"PTA装置", "MEG装置", "PX装置"}
+	readLimit := 3
+	for _, nameKey := range taskNames {
+		fetchRule, e := loadDataRule(nameKey)
+		if e != nil {
+			utils.FileLog.Info(fmt.Sprintf("%s无解析规则, err: %v\n", nameKey, e))
+			continue
+		}
+
+		// 解析前N篇报告
+		files, e := savePageHtml(nameKey, fetchRule.PageDir, false, readLimit)
+		if e != nil {
+			utils.FileLog.Info(fmt.Sprintf("%s保存首页失败, err: %v\n", nameKey, e))
+			continue
+		}
+		readCount := 0
+		for _, v := range files {
+			readCount += 1
+			if readCount > readLimit {
+				break
+			}
+			htm, e := ioutil.ReadFile(v)
+			if e != nil {
+				fmt.Printf("file: %s, ReadFile err: %v\n", v, e)
+				utils.FileLog.Info(fmt.Sprintf("file: %s, ReadFile err: %v", v, e))
+				continue
+			}
+			tableContent, tableDate, e := AnalysisStockTable(htm)
+			if e != nil {
+				fmt.Printf("file: %s, AnalysisStockTable err: %v\n", v, e)
+				utils.FileLog.Info(fmt.Sprintf("file: %s, AnalysisStockTable err: %v", v, e))
+				continue
+			}
+			tableItem := new(HandleTableData)
+			tableItem.ClassifyId = fetchRule.StockTable.ClassifyId
+			tableItem.FromPage = v
+			tableItem.TableDate = tableDate
+			tableItem.TableContent = tableContent
+
+			// 写入数据库
+			params := make(map[string]interface{})
+			params["Table"] = tableItem
+			params["TerminalCode"] = utils.TerminalCode
+			result, e := postEdbLib(params, utils.LIB_ROUTE_CCF_TABLE_HANDLE)
+			if e != nil {
+				b, _ := json.Marshal(params)
+				fmt.Printf("file: %s, postEdbLib err: %v, params: %s\n", v, e, string(b))
+				utils.FileLog.Info(fmt.Sprintf("file: %s, postEdbLib err: %v, params: %s", v, e, string(b)))
+				continue
+			}
+			resp := new(models.BaseEdbLibResponse)
+			if e = json.Unmarshal(result, &resp); e != nil {
+				fmt.Printf("file: %s, json.Unmarshal err: %v\n", v, e)
+				utils.FileLog.Info(fmt.Sprintf("file: %s, json.Unmarshal err: %v", v, e))
+				continue
+			}
+			if resp.Ret != 200 {
+				fmt.Printf("file: %s, Msg: %s, ErrMsg: %s\n", v, resp.Msg, resp.ErrMsg)
+				utils.FileLog.Info(fmt.Sprintf("file: %s, Msg: %s, ErrMsg: %s", v, resp.Msg, resp.ErrMsg))
+				continue
+			}
+		}
+	}
+	return
+}
+
+// AnalysisStockTable 解析装置表格
+func AnalysisStockTable(htm []byte) (tableContent string, tableTime time.Time, err error) {
+	doc, e := goquery.NewDocumentFromReader(strings.NewReader(string(htm)))
+	if e != nil {
+		err = fmt.Errorf("NewDocumentFromReader err: %v", e)
+		return
+	}
+
+	// 从收藏按钮往上找table, 取出报告发布日期
+	collectEle := doc.Find("#savenews")
+	publishTimeTab := collectEle.ParentsFiltered("table").First()
+	publishTxt := publishTimeTab.Find("td:first-child").Text()
+	//fmt.Println("publishTxt: ", publishTxt)
+	publishTime, e := extractReportPublishTime(publishTxt)
+	if e != nil {
+		err = fmt.Errorf("extractReportPublishTime err: %v", e)
+		return
+	}
+	if publishTime.IsZero() {
+		err = fmt.Errorf("发布日期有误")
+		return
+	}
+	//fmt.Println(publishTime)
+	tableTime = publishTime
+	//publishYear := publishTime.Year()
+	//fmt.Println(publishYear)
+
+	// 查找包含关键词的标签
+	keyElement := doc.Find("#newscontent")
+	table := keyElement.ChildrenFiltered("table").First()
+	if table.Length() <= 0 {
+		err = fmt.Errorf("表格未找到")
+		return
+	}
+	h, e := table.Html()
+	if e != nil {
+		err = fmt.Errorf("表格HTML有误, err: %v", e)
+		return
+	}
+	tableContent = fmt.Sprintf("<table>%s</table>", h)
+	//tableContent = regexp.MustCompile(`\n`).ReplaceAllString(tableContent, "")
+	return
+}
+
+// ReadStockHistoryFiles 读取历史文件
+func ReadStockHistoryFiles(context.Context) {
+	var err error
+	defer func() {
+		if err != nil {
+			tips := fmt.Sprintf("ReadStockHistoryFiles ErrMsg: %s", err.Error())
+			utils.FileLog.Info(tips)
+			fmt.Println(tips)
+		}
+	}()
+
+	taskNames := []string{"PTA装置", "MEG装置", "PX装置"}
+	for _, nameKey := range taskNames {
+		fetchRule, e := loadDataRule(nameKey)
+		if e != nil {
+			utils.FileLog.Info(fmt.Sprintf("%s无解析规则, err: %v\n", nameKey, e))
+			continue
+		}
+
+		filePaths, e := listFiles(fetchRule.PageDir)
+		if e != nil {
+			utils.FileLog.Info(fmt.Sprintf("%s读取文件目录失败, err: %v\n", nameKey, e))
+			continue
+		}
+
+		for _, v := range filePaths {
+			v = fmt.Sprintf("%s/%s", fetchRule.PageDir, v)
+			fmt.Printf("开始解析: %s", v)
+			//htm, e := ioutil.ReadFile("static/ccf/oil_daily/原油石化早报(4.18).html")
+			htm, e := ioutil.ReadFile(v)
+			if e != nil {
+				fmt.Printf("file: %s, ReadFile err: %v\n", v, e)
+				utils.FileLog.Info(fmt.Sprintf("file: %s, ReadFile err: %v", v, e))
+				continue
+			}
+			tableContent, tableDate, e := AnalysisStockTable(htm)
+			if e != nil {
+				fmt.Printf("file: %s, AnalysisStockTable err: %v\n", v, e)
+				utils.FileLog.Info(fmt.Sprintf("file: %s, AnalysisStockTable err: %v", v, e))
+				continue
+			}
+			tableItem := new(HandleTableData)
+			tableItem.ClassifyId = fetchRule.StockTable.ClassifyId
+			tableItem.FromPage = v
+			tableItem.TableDate = tableDate
+			tableItem.TableContent = tableContent
+
+			// 写入数据库
+			params := make(map[string]interface{})
+			params["Table"] = tableItem
+			params["TerminalCode"] = utils.TerminalCode
+			result, e := postEdbLib(params, utils.LIB_ROUTE_CCF_TABLE_HANDLE)
+			if e != nil {
+				b, _ := json.Marshal(params)
+				fmt.Printf("file: %s, postEdbLib err: %v, params: %s\n", v, e, string(b))
+				utils.FileLog.Info(fmt.Sprintf("file: %s, postEdbLib err: %v, params: %s", v, e, string(b)))
+				continue
+			}
+			resp := new(models.BaseEdbLibResponse)
+			if e = json.Unmarshal(result, &resp); e != nil {
+				fmt.Printf("file: %s, json.Unmarshal err: %v\n", v, e)
+				utils.FileLog.Info(fmt.Sprintf("file: %s, json.Unmarshal err: %v", v, e))
+				continue
+			}
+			if resp.Ret != 200 {
+				fmt.Printf("file: %s, Msg: %s, ErrMsg: %s\n", v, resp.Msg, resp.ErrMsg)
+				utils.FileLog.Info(fmt.Sprintf("file: %s, Msg: %s, ErrMsg: %s", v, resp.Msg, resp.ErrMsg))
+				continue
+			}
+		}
+	}
+	return
+}

+ 19 - 0
services/task.go

@@ -1,6 +1,7 @@
 package services
 
 import (
+	ccfService "eta/eta_data_analysis/services/base_from_ccf"
 	"eta/eta_data_analysis/utils"
 	"fmt"
 	"github.com/beego/beego/v2/task"
@@ -62,6 +63,24 @@ func Task() {
 		task.AddTask("启动获取邮件附件脚本", mailAttachment)
 	}
 
+	// CCF化纤信息
+	if utils.CCFOpen == "1" {
+		// 原油石化早报-每日9:00
+		taskCCFOilDailyEdb := task.NewTask("taskCCFOilDailyEdb", "0 30 17 * * *", ccfService.TaskOilDailyEdb)
+		task.AddTask("CCF原油石化早报", taskCCFOilDailyEdb)
+
+		// 各品种周报-每周五17-19点每隔半小时
+		taskCCFWeeklyEdb := task.NewTask("taskCCFWeeklyEdb", "0 */30 17 * * 5", ccfService.TaskWeeklyEdb)
+		task.AddTask("CCF周度指标", taskCCFWeeklyEdb)
+
+		// 各品种装置-每周四15-18点每隔半小时
+		taskCCFStockTable := task.NewTask("taskCCFStockTable", "0 */30 15-18 * * 4", ccfService.TaskStockTable)
+		task.AddTask("CCF装置检修", taskCCFStockTable)
+
+		//var ctx context.Context
+		//go ccfService.ReadEdbHistoryFiles(ctx)
+	}
+
 	task.StartTask()
 
 	fmt.Println("task end")

+ 880 - 0
static/ccf_data_rule.json

@@ -0,0 +1,880 @@
+[
+  {
+    "Name": "原油石化早报",
+    "PageDir": "static/ccf/oil_daily",
+    "Search": {
+      "ClassId": "400000",
+      "SubClassId": "410000",
+      "ProductId": "10000",
+      "SubProductId": "110000",
+      "SimpleTerms": "原油石化"
+    },
+    "TableFetch": [],
+    "EdbMatch": [
+      {
+        "IndexCode": "ccfyyqhwtijg",
+        "IndexName": "CCF原油期货WTI价格",
+        "ClassifyId": 3,
+        "Frequency": "日度",
+        "Product": "原油期货",
+        "Market": "WTI",
+        "MatchUnit": "美元/桶",
+        "Unit": "美元/桶"
+      },
+      {
+        "IndexCode": "ccfyyqhbrentjg",
+        "IndexName": "CCF原油期货Brent价格",
+        "ClassifyId": 3,
+        "Frequency": "日度",
+        "Product": "原油期货",
+        "Market": "Brent",
+        "MatchUnit": "美元/桶",
+        "Unit": "美元/桶"
+      },
+      {
+        "IndexCode": "ccfhlrmbzjj",
+        "IndexName": "CCF汇率人民币中间价",
+        "ClassifyId": 4,
+        "Frequency": "日度",
+        "Product": "汇率",
+        "Market": "人民币中间价",
+        "MatchUnit": "",
+        "Unit": "无"
+      },
+      {
+        "IndexCode": "ccfsnycfrrbjg",
+        "IndexName": "CCF石脑油CFR日本价格",
+        "ClassifyId": 5,
+        "Frequency": "日度",
+        "Product": "石脑油",
+        "Market": "CFR日本",
+        "MatchUnit": "美元/吨",
+        "Unit": "美元/吨"
+      },
+      {
+        "IndexCode": "ccfsnyfobxjpjg",
+        "IndexName": "CCF石脑油FOB新加坡价格",
+        "ClassifyId": 5,
+        "Frequency": "日度",
+        "Product": "石脑油",
+        "Market": "FOB新加坡",
+        "MatchUnit": "美元/桶",
+        "Unit": "美元/桶"
+      },
+      {
+        "IndexCode": "ccfygjmxfobhgjg",
+        "IndexName": "CCF异构级MX/FOB韩国价格",
+        "ClassifyId": 6,
+        "Frequency": "日度",
+        "Product": "异构级MX",
+        "Market": "FOB韩国",
+        "MatchUnit": "美元/吨",
+        "Unit": "美元/吨"
+      },
+      {
+        "IndexCode": "ccfygjmxcfrzgjg",
+        "IndexName": "CCF异构级MX/CFR中国价格",
+        "ClassifyId": 6,
+        "Frequency": "日度",
+        "Product": "异构级MX",
+        "Market": "CFR中国",
+        "MatchUnit": "美元/吨",
+        "Unit": "美元/吨"
+      },
+      {
+        "IndexCode": "ccfygjmxfobmwjg",
+        "IndexName": "CCF异构级MX/FOB美湾价格",
+        "ClassifyId": 6,
+        "Frequency": "日度",
+        "Product": "异构级MX",
+        "Market": "FOB美湾",
+        "MatchUnit": "美元/吨",
+        "Unit": "美元/吨"
+      },
+      {
+        "IndexCode": "ccfpxfobhgjg",
+        "IndexName": "CCF/PX/FOB韩国价格",
+        "ClassifyId": 7,
+        "Frequency": "日度",
+        "Product": "PX",
+        "Market": "FOB韩国",
+        "MatchUnit": "美元/吨",
+        "Unit": "美元/吨"
+      },
+      {
+        "IndexCode": "ccfpxcfrzgjg",
+        "IndexName": "CCF/PX/CFR中国价格",
+        "ClassifyId": 7,
+        "Frequency": "日度",
+        "Product": "PX",
+        "Market": "CFR中国",
+        "MatchUnit": "美元/吨",
+        "Unit": "美元/吨"
+      },
+      {
+        "IndexCode": "ccfpxfobltdjg",
+        "IndexName": "CCF/PX/FOB鹿特丹价格",
+        "ClassifyId": 7,
+        "Frequency": "日度",
+        "Product": "PX",
+        "Market": "FOB鹿特丹",
+        "MatchUnit": "美元/吨",
+        "Unit": "美元/吨"
+      },
+      {
+        "IndexCode": "ccfpxfobmghwjg",
+        "IndexName": "CCF/PX/FOB美国海湾价格",
+        "ClassifyId": 7,
+        "Frequency": "日度",
+        "Product": "PX",
+        "Market": "FOB美国海湾",
+        "MatchUnit": "美元/吨",
+        "Unit": "美元/吨"
+      },
+      {
+        "IndexCode": "ccfcbfobhgjg",
+        "IndexName": "CCF纯苯FOB韩国价格",
+        "ClassifyId": 8,
+        "Frequency": "日度",
+        "Product": "纯苯",
+        "Market": "FOB韩国",
+        "MatchUnit": "美元/吨",
+        "Unit": "美元/吨"
+      },
+      {
+        "IndexCode": "ccfcbfobltdjg",
+        "IndexName": "CCF纯苯FOB鹿特丹价格",
+        "ClassifyId": 8,
+        "Frequency": "日度",
+        "Product": "纯苯",
+        "Market": "FOB鹿特丹",
+        "MatchUnit": "美元/吨",
+        "Unit": "美元/吨"
+      },
+      {
+        "IndexCode": "ccfcbfobmwjg",
+        "IndexName": "CCF纯苯FOB美湾价格",
+        "ClassifyId": 8,
+        "Frequency": "日度",
+        "Product": "纯苯",
+        "Market": "FOB美湾",
+        "MatchUnit": "美元/吨",
+        "Unit": "美元/吨"
+      },
+      {
+        "IndexCode": "ccfcbcfrzgjg",
+        "IndexName": "CCF纯苯CFR中国价格",
+        "ClassifyId": 8,
+        "Frequency": "日度",
+        "Product": "纯苯",
+        "Market": "CFR中国",
+        "MatchUnit": "美元/吨",
+        "Unit": "美元/吨"
+      },
+      {
+        "IndexCode": "ccfjbfobhgjg",
+        "IndexName": "CCF甲苯FOB韩国价格",
+        "ClassifyId": 9,
+        "Frequency": "日度",
+        "Product": "甲苯",
+        "Market": "FOB韩国",
+        "MatchUnit": "美元/吨",
+        "Unit": "美元/吨"
+      },
+      {
+        "IndexCode": "ccfjbfobltdjg",
+        "IndexName": "CCF甲苯FOB鹿特丹价格",
+        "ClassifyId": 9,
+        "Frequency": "日度",
+        "Product": "甲苯",
+        "Market": "FOB鹿特丹",
+        "MatchUnit": "美元/吨",
+        "Unit": "美元/吨"
+      },
+      {
+        "IndexCode": "ccfjbcfrzgjg",
+        "IndexName": "CCF甲苯CFR中国价格",
+        "ClassifyId": 9,
+        "Frequency": "日度",
+        "Product": "甲苯",
+        "Market": "CFR中国",
+        "MatchUnit": "美元/吨",
+        "Unit": "美元/吨"
+      },
+      {
+        "IndexCode": "ccfjbfobmwjg",
+        "IndexName": "CCF甲苯FOB美湾价格",
+        "ClassifyId": 9,
+        "Frequency": "日度",
+        "Product": "甲苯",
+        "Market": "FOB美湾",
+        "MatchUnit": "美元/吨",
+        "Unit": "美元/吨"
+      },
+      {
+        "IndexCode": "ccfbyxcfrzgjg",
+        "IndexName": "CCF苯乙烯CFR中国价格",
+        "ClassifyId": 10,
+        "Frequency": "日度",
+        "Product": "苯乙烯",
+        "Market": "CFR中国",
+        "MatchUnit": "美元/吨",
+        "Unit": "美元/吨"
+      },
+      {
+        "IndexCode": "ccfbyxfobhgjg",
+        "IndexName": "CCF苯乙烯FOB韩国价格",
+        "ClassifyId": 10,
+        "Frequency": "日度",
+        "Product": "苯乙烯",
+        "Market": "FOB韩国",
+        "MatchUnit": "美元/吨",
+        "Unit": "美元/吨"
+      },
+      {
+        "IndexCode": "ccfbyxfobltdjg",
+        "IndexName": "CCF苯乙烯FOB鹿特丹价格",
+        "ClassifyId": 10,
+        "Frequency": "日度",
+        "Product": "苯乙烯",
+        "Market": "FOB鹿特丹",
+        "MatchUnit": "美元/吨",
+        "Unit": "美元/吨"
+      },
+      {
+        "IndexCode": "ccfbyxfobmwjg",
+        "IndexName": "CCF苯乙烯FOB美湾价格",
+        "ClassifyId": 10,
+        "Frequency": "日度",
+        "Product": "苯乙烯",
+        "Market": "FOB美湾",
+        "MatchUnit": "美元/吨",
+        "Unit": "美元/吨"
+      },
+      {
+        "IndexCode": "ccfyxcfrdbyjg",
+        "IndexName": "CCF乙烯CFR东北亚价格",
+        "ClassifyId": 11,
+        "Frequency": "日度",
+        "Product": "乙烯",
+        "Market": "CFR东北亚",
+        "MatchUnit": "美元/吨",
+        "Unit": "美元/吨"
+      },
+      {
+        "IndexCode": "ccfyxcfrdnyjg",
+        "IndexName": "CCF乙烯CFR东南亚价格",
+        "ClassifyId": 11,
+        "Frequency": "日度",
+        "Product": "乙烯",
+        "Market": "CFR东南亚",
+        "MatchUnit": "美元/吨",
+        "Unit": "美元/吨"
+      },
+      {
+        "IndexCode": "ccfyxfdmwjg",
+        "IndexName": "CCF乙烯FD美湾价格",
+        "ClassifyId": 11,
+        "Frequency": "日度",
+        "Product": "乙烯",
+        "Market": "FD美湾",
+        "MatchUnit": "美分/磅",
+        "Unit": "美分/磅"
+      },
+      {
+        "IndexCode": "ccfbxfobhgjg",
+        "IndexName": "CCF丙烯FOB韩国价格",
+        "ClassifyId": 12,
+        "Frequency": "日度",
+        "Product": "丙烯",
+        "Market": "FOB韩国",
+        "MatchUnit": "美元/吨",
+        "Unit": "美元/吨"
+      },
+      {
+        "IndexCode": "ccfbxcfrzgjg",
+        "IndexName": "CCF丙烯CFR中国价格",
+        "ClassifyId": 12,
+        "Frequency": "日度",
+        "Product": "丙烯",
+        "Market": "CFR中国",
+        "MatchUnit": "美元/吨",
+        "Unit": "美元/吨"
+      },
+      {
+        "IndexCode": "ccfbxfdmwjg",
+        "IndexName": "CCF丙烯FD美湾价格",
+        "ClassifyId": 12,
+        "Frequency": "日度",
+        "Product": "丙烯",
+        "Market": "FD美湾",
+        "MatchUnit": "美分/磅",
+        "Unit": "美分/磅"
+      },
+      {
+        "IndexCode": "ccfdexcfrzgjg",
+        "IndexName": "CCF丙烯CFR中国价格",
+        "ClassifyId": 13,
+        "Frequency": "日度",
+        "Product": "丁二烯",
+        "Market": "CFR中国",
+        "MatchUnit": "美元/吨",
+        "Unit": "美元/吨"
+      },
+      {
+        "IndexCode": "ccfdexfobhgjg",
+        "IndexName": "CCF丙烯FOB韩国价格",
+        "ClassifyId": 13,
+        "Frequency": "日度",
+        "Product": "丁二烯",
+        "Market": "FOB韩国",
+        "MatchUnit": "美元/吨",
+        "Unit": "美元/吨"
+      }
+    ]
+  },
+  {
+    "Name": "PTA周报",
+    "PageDir": "static/ccf/pta_week",
+    "Search": {
+      "ClassId": "100000",
+      "SubClassId": "160000",
+      "ProductId": "200000",
+      "SubProductId": "210000",
+      "SimpleTerms": "PTA"
+    },
+    "TableFetch": [
+      {
+        "Keyword": "负荷"
+      },
+      {
+        "Keyword": "PTA库存"
+      }
+    ],
+    "EdbMatch": [
+      {
+        "IndexCode": "ccfpxzgdlfh",
+        "IndexName": "CCF/PX中国大陆负荷",
+        "ClassifyId": 14,
+        "Frequency": "周度",
+        "Product": "PX",
+        "Market": "中国",
+        "MatchUnit": "",
+        "Unit": "%"
+      },
+      {
+        "IndexCode": "ccfptazgdlfh",
+        "IndexName": "CCF/PTA中国大陆负荷",
+        "ClassifyId": 14,
+        "Frequency": "周度",
+        "Product": "PTA",
+        "Market": "中国",
+        "MatchUnit": "",
+        "Unit": "%"
+      },
+      {
+        "IndexCode": "ccfjzzgdlfh",
+        "IndexName": "CCF聚酯中国大陆负荷",
+        "ClassifyId": 14,
+        "Frequency": "周度",
+        "Product": "聚酯",
+        "Market": "中国",
+        "MatchUnit": "",
+        "Unit": "%"
+      },
+      {
+        "IndexCode": "ccfptagckcts",
+        "IndexName": "CCF/PTA工厂库存天数",
+        "ClassifyId": 14,
+        "Frequency": "周度",
+        "Product": "PTA工厂",
+        "Market": "",
+        "MatchUnit": "",
+        "Unit": "天"
+      },
+      {
+        "IndexCode": "ccfjzgckcts",
+        "IndexName": "CCF聚酯工厂库存天数",
+        "ClassifyId": 14,
+        "Frequency": "周度",
+        "Product": "聚酯工厂",
+        "Market": "",
+        "MatchUnit": "",
+        "Unit": "天"
+      }
+    ]
+  },
+  {
+    "Name": "MEG周报",
+    "PageDir": "static/ccf/meg_week",
+    "Search": {
+      "ClassId": "100000",
+      "SubClassId": "160000",
+      "ProductId": "200000",
+      "SubProductId": "220000",
+      "SimpleTerms": "MEG"
+    },
+    "TableFetch": [
+      {
+        "Keyword": "CCF指数"
+      },
+      {
+        "Keyword": "MEG华东港口库存情况"
+      }
+    ],
+    "EdbMatch": [
+      {
+        "IndexCode": "ccfmegfhgnzfh",
+        "IndexName": "CCF/MEG负荷国内总负荷",
+        "ClassifyId": 15,
+        "Frequency": "周度",
+        "Product": "MEG负荷",
+        "Market": "国内总负荷",
+        "MatchUnit": "",
+        "Unit": "%"
+      },
+      {
+        "IndexCode": "ccfmegfhgnhcqzfh",
+        "IndexName": "CCF/MEG负荷国内合成气制负荷",
+        "ClassifyId": 15,
+        "Frequency": "周度",
+        "Product": "MEG负荷",
+        "Market": "国内合成气制负荷",
+        "MatchUnit": "",
+        "Unit": "%"
+      },
+      {
+        "IndexCode": "ccfmeghdgkkcnbg",
+        "IndexName": "CCF/MEG华东港口库存宁波港",
+        "ClassifyId": 15,
+        "Frequency": "周度",
+        "Product": "宁波港",
+        "Market": "",
+        "MatchUnit": "",
+        "Unit": "万吨"
+      },
+      {
+        "IndexCode": "ccfmeghdgkkcshcs",
+        "IndexName": "CCF/MEG华东港口库存上海&常熟",
+        "ClassifyId": 15,
+        "Frequency": "周度",
+        "Product": "上海&常熟",
+        "Market": "",
+        "MatchUnit": "",
+        "Unit": "万吨"
+      },
+      {
+        "IndexCode": "ccfmeghdgkkczjg",
+        "IndexName": "CCF/MEG华东港口库存张家港",
+        "ClassifyId": 15,
+        "Frequency": "周度",
+        "Product": "张家港",
+        "Market": "",
+        "MatchUnit": "",
+        "Unit": "万吨"
+      },
+      {
+        "IndexCode": "ccfmeghdgkkctc",
+        "IndexName": "CCF/MEG华东港口库存太仓",
+        "ClassifyId": 15,
+        "Frequency": "周度",
+        "Product": "太仓",
+        "Market": "",
+        "MatchUnit": "",
+        "Unit": "万吨"
+      },
+      {
+        "IndexCode": "ccfmeghdgkkcjycz",
+        "IndexName": "CCF/MEG华东港口库存江阴&常州",
+        "ClassifyId": 15,
+        "Frequency": "周度",
+        "Product": "江阴&常州",
+        "Market": "",
+        "MatchUnit": "",
+        "Unit": "万吨"
+      },
+      {
+        "IndexCode": "ccfmeghdgkkcqt",
+        "IndexName": "CCF/MEG华东港口库存其他",
+        "ClassifyId": 15,
+        "Frequency": "周度",
+        "Product": "其他",
+        "Market": "",
+        "MatchUnit": "",
+        "Unit": "万吨"
+      },
+      {
+        "IndexCode": "ccfmeghdgkkchddqzkc",
+        "IndexName": "CCF/MEG华东港口库存华东地区总库存",
+        "ClassifyId": 15,
+        "Frequency": "周度",
+        "Product": "华东地区总库存",
+        "Market": "",
+        "MatchUnit": "",
+        "Unit": "万吨"
+      }
+    ]
+  },
+  {
+    "Name": "长丝周报",
+    "PageDir": "static/ccf/changsi_week",
+    "Search": {
+      "ClassId": "100000",
+      "SubClassId": "160000",
+      "ProductId": "200000",
+      "SubProductId": "230000",
+      "SimpleTerms": "长丝"
+    },
+    "TableIgnore": [],
+    "TableFetch": [
+      {
+        "Keyword": "负荷指数"
+      },
+      {
+        "Keyword": "库存指数"
+      },
+      {
+        "Keyword": "下游观察"
+      }
+    ],
+    "EdbMatch": [
+      {
+        "IndexCode": "ccfjzfh",
+        "IndexName": "CCF聚酯负荷",
+        "ClassifyId": 16,
+        "Frequency": "周度",
+        "Product": "聚酯负荷",
+        "Market": "",
+        "MatchUnit": "",
+        "Unit": "%"
+      },
+      {
+        "IndexCode": "ccfcsfh",
+        "IndexName": "CCF长丝负荷",
+        "ClassifyId": 16,
+        "Frequency": "周度",
+        "Product": "长丝负荷",
+        "Market": "",
+        "MatchUnit": "",
+        "Unit": "%"
+      },
+      {
+        "IndexCode": "ccfzfcsfh",
+        "IndexName": "CCF直纺长丝负荷",
+        "ClassifyId": 16,
+        "Frequency": "周度",
+        "Product": "直纺长丝",
+        "Market": "",
+        "MatchUnit": "",
+        "Unit": "%"
+      },
+      {
+        "IndexCode": "ccfqpfcsfh",
+        "IndexName": "CCF切片纺长丝负荷",
+        "ClassifyId": 16,
+        "Frequency": "周度",
+        "Product": "切片纺长丝",
+        "Market": "",
+        "MatchUnit": "",
+        "Unit": "%"
+      },
+      {
+        "IndexCode": "ccfjzscpoykcts",
+        "IndexName": "CCF江浙市场POY库存天数",
+        "ClassifyId": 16,
+        "Frequency": "周度",
+        "Product": "江浙市场",
+        "Market": "POY",
+        "MatchUnit": "",
+        "Unit": "天"
+      },
+      {
+        "IndexCode": "ccfjzscpdykcts",
+        "IndexName": "CCF江浙市场FDY库存天数",
+        "ClassifyId": 16,
+        "Frequency": "周度",
+        "Product": "江浙市场",
+        "Market": "FDY",
+        "MatchUnit": "",
+        "Unit": "天"
+      },
+      {
+        "IndexCode": "ccfjzscdtykcts",
+        "IndexName": "CCF江浙市场DTY库存天数",
+        "ClassifyId": 16,
+        "Frequency": "周度",
+        "Product": "江浙市场",
+        "Market": "DTY",
+        "MatchUnit": "",
+        "Unit": "天"
+      },
+      {
+        "IndexCode": "ccffjscfdykcts",
+        "IndexName": "CCF福建市场FDY库存天数",
+        "ClassifyId": 16,
+        "Frequency": "周度",
+        "Product": "福建市场",
+        "Market": "FDY",
+        "MatchUnit": "",
+        "Unit": "天"
+      },
+      {
+        "IndexCode": "ccffjscdtykcts",
+        "IndexName": "CCF福建市场DTY库存天数",
+        "ClassifyId": 16,
+        "Frequency": "周度",
+        "Product": "福建市场",
+        "Market": "DTY",
+        "MatchUnit": "",
+        "Unit": "天"
+      },
+      {
+        "IndexCode": "ccfjzjtxyfh",
+        "IndexName": "CCF江浙加弹下游负荷",
+        "ClassifyId": 16,
+        "Frequency": "周度",
+        "Product": "江浙加弹",
+        "Market": "",
+        "MatchUnit": "",
+        "Unit": "%"
+      },
+      {
+        "IndexCode": "ccfjzzjxyfh",
+        "IndexName": "CCF江浙织机下游负荷",
+        "ClassifyId": 16,
+        "Frequency": "周度",
+        "Product": "江浙织机",
+        "Market": "",
+        "MatchUnit": "",
+        "Unit": "%"
+      },
+      {
+        "IndexCode": "ccfhnzjxyfh",
+        "IndexName": "CCF华南织机下游负荷",
+        "ClassifyId": 16,
+        "Frequency": "周度",
+        "Product": "华南织机",
+        "Market": "",
+        "MatchUnit": "",
+        "Unit": "%"
+      },
+      {
+        "IndexCode": "ccfjzyrxyfh",
+        "IndexName": "CCF江浙印染下游负荷",
+        "ClassifyId": 16,
+        "Frequency": "周度",
+        "Product": "江浙印染",
+        "Market": "",
+        "MatchUnit": "",
+        "Unit": "%"
+      }
+    ]
+  },
+  {
+    "Name": "短纤周报",
+    "PageDir": "static/ccf/duanxian_week",
+    "Search": {
+      "ClassId": "100000",
+      "SubClassId": "160000",
+      "ProductId": "200000",
+      "SubProductId": "240000",
+      "SimpleTerms": "涤短"
+    },
+    "TableFetch": [
+      {
+        "Keyword": "主要运行指数"
+      }
+    ],
+    "EdbMatch": [
+      {
+        "IndexCode": "ccfddkczs",
+        "IndexName": "CCF涤短库存指数",
+        "ClassifyId": 17,
+        "Frequency": "周度",
+        "Product": "涤短库存指数",
+        "Market": "",
+        "MatchUnit": "",
+        "Unit": "%"
+      },
+      {
+        "IndexCode": "ccfddkjzs",
+        "IndexName": "CCF涤短开机指数",
+        "ClassifyId": 17,
+        "Frequency": "周度",
+        "Product": "涤短开机指数",
+        "Market": "",
+        "MatchUnit": "",
+        "Unit": "%"
+      }
+    ]
+  },
+  {
+    "Name": "瓶片周报",
+    "PageDir": "static/ccf/pingpian_week",
+    "Search": {
+      "ClassId": "100000",
+      "SubClassId": "160000",
+      "ProductId": "200000",
+      "SubProductId": "250000",
+      "SimpleTerms": "瓶片"
+    },
+    "TableFetch": [
+      {
+        "Keyword": "周均负荷指数"
+      }
+    ],
+    "EdbMatch": [
+      {
+        "IndexCode": "ccfppazgclfh",
+        "IndexName": "CCF瓶片按最高产量负荷",
+        "ClassifyId": 18,
+        "Frequency": "周度",
+        "Product": "按最高产量",
+        "Market": "",
+        "MatchUnit": "",
+        "Unit": "%"
+      },
+      {
+        "IndexCode": "ccfppasjcnfh",
+        "IndexName": "CCF瓶片按设计产能负荷",
+        "ClassifyId": 18,
+        "Frequency": "周度",
+        "Product": "按设计产能",
+        "Market": "",
+        "MatchUnit": "",
+        "Unit": "%"
+      }
+    ]
+  },
+  {
+    "Name": "切片周报",
+    "PageDir": "static/ccf/qiepian_week",
+    "Search": {
+      "ClassId": "100000",
+      "SubClassId": "160000",
+      "ProductId": "200000",
+      "SubProductId": "260000",
+      "SimpleTerms": "切片"
+    },
+    "TableFetch": [
+      {
+        "Keyword": "切片纺方面"
+      }
+    ],
+    "EdbMatch": [
+      {
+        "IndexCode": "ccfqhfgcqpkc",
+        "IndexName": "CCF切片纺工厂切片库存",
+        "ClassifyId": 19,
+        "Frequency": "周度",
+        "Product": "切片纺工厂切片库存",
+        "Market": "",
+        "MatchUnit": "",
+        "Unit": "天"
+      }
+    ]
+  },
+  {
+    "Name": "PX周报",
+    "PageDir": "static/ccf/px_week",
+    "Search": {
+      "ClassId": "100000",
+      "SubClassId": "160000",
+      "ProductId": "C00000",
+      "SubProductId": "C10000",
+      "SimpleTerms": "PX"
+    },
+    "TableFetch": [
+      {
+        "Keyword": "负荷指数"
+      }
+    ],
+    "EdbMatch": [
+      {
+        "IndexCode": "ccfzgpxfh",
+        "IndexName": "CCF中国PX负荷",
+        "ClassifyId": 20,
+        "Frequency": "周度",
+        "Product": "中国PX",
+        "Market": "",
+        "MatchUnit": "",
+        "Unit": "%"
+      },
+      {
+        "IndexCode": "ccfyzpxfh",
+        "IndexName": "CCF亚洲PX负荷",
+        "ClassifyId": 20,
+        "Frequency": "周度",
+        "Product": "亚洲PX",
+        "Market": "",
+        "MatchUnit": "",
+        "Unit": "%"
+      },
+      {
+        "IndexCode": "ccfzgptafh",
+        "IndexName": "CCF中国PTA负荷",
+        "ClassifyId": 20,
+        "Frequency": "周度",
+        "Product": "中国PTA",
+        "Market": "",
+        "MatchUnit": "",
+        "Unit": "%"
+      },
+      {
+        "IndexCode": "ccfzgjzfh",
+        "IndexName": "CCF中国聚酯负荷",
+        "ClassifyId": 20,
+        "Frequency": "周度",
+        "Product": "中国聚酯",
+        "Market": "",
+        "MatchUnit": "",
+        "Unit": "%"
+      }
+    ]
+  },
+  {
+    "Name": "PTA装置",
+    "PageDir": "static/ccf/pta_stock",
+    "Search": {
+      "ClassId": "100000",
+      "SubClassId": "1A0000",
+      "ProductId": "200000",
+      "SubProductId": "210000",
+      "SimpleTerms": "本周PTA"
+    },
+    "TableFetch": [],
+    "StockTable": {
+      "ClassifyId": 1
+    }
+  },
+  {
+    "Name": "MEG装置",
+    "PageDir": "static/ccf/meg_stock",
+    "Search": {
+      "ClassId": "100000",
+      "SubClassId": "1A0000",
+      "ProductId": "200000",
+      "SubProductId": "220000",
+      "SimpleTerms": "本周国内外MEG"
+    },
+    "TableFetch": [],
+    "StockTable": {
+      "ClassifyId": 2
+    }
+  },
+  {
+    "Name": "PX装置",
+    "PageDir": "static/ccf/px_stock",
+    "Search": {
+      "ClassId": "100000",
+      "SubClassId": "1A0000",
+      "ProductId": "C00000",
+      "SubProductId": "C10000",
+      "SimpleTerms": "本周亚洲PX"
+    },
+    "TableFetch": [],
+    "StockTable": {
+      "ClassifyId": 3
+    }
+  }
+]

+ 14 - 0
utils/config.go

@@ -67,6 +67,13 @@ var (
 	MtjhOpen     string //是否配置煤炭江湖数据源,1已配置
 )
 
+// CCF化纤信息
+var (
+	CCFOpen         string // 是否配置CCF
+	CCFCookieFile   string // CCF登录Cookie
+	CCFDataRuleFile string // CCF数据爬取规则
+)
+
 var TerminalCode string
 
 func init() {
@@ -141,6 +148,13 @@ func init() {
 	}
 
 	TerminalCode = config["terminal_code"]
+
+	// CCF化纤信息
+	{
+		CCFOpen = config["ccf_open"]
+		CCFCookieFile = config["ccf_cookie_file"]
+		CCFDataRuleFile = config["ccf_data_rule_file"]
+	}
 }
 
 //修改接口文档

+ 2 - 0
utils/constants.go

@@ -244,4 +244,6 @@ const (
 	LIB_ROUTE_FENWEI_CLASSIFY           = "fenwei/classify_tree"       // 汾渭煤炭分类接口地址
 	LIB_ROUTE_FENWEI_INDEX_LIST         = "fenwei/base_index_list"     // 汾渭煤炭指标列表接口地址
 	LIB_ROUTE_COAL_MINE_MTJH            = "/mtjh/data"                 //煤炭江湖数据处理excel数据并入库 数据地址
+	LIB_ROUTE_CCF_EDB_HANDLE            = "ccf/handle/edb_data"        // CCF化纤信息指标入库接口地址
+	LIB_ROUTE_CCF_TABLE_HANDLE          = "ccf/handle/table_data"      // CCF化纤信息装置表格入库接口地址
 )